jvm GC怎么掌握
这篇文章主要介绍"jvm GC怎么掌握",在日常操作中,相信很多人在jvm GC怎么掌握问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"jvm GC怎么掌握"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
在学习GC之前,你首先应该记住一个单词:"stop-the-world"。Stop-the-world会在任何一种GC算法中发生。Stop-the-world意味着 JVM 因为要执行GC而停止了应用程序的执行。当Stop-the-world发生时,除了GC所需的线程以外,所有线程都处于等待状态,直到GC任务完成。GC优化很多时候就是指减少Stop-the-world发生的时间。
新生代(Young generation)对象从这个区域消失的过程我们称之为"minor GC"
老年代(Old generation)对象从老年代中消失的过程,我们称之为"major GC"(或者"full GC")
持久代( permanent generation )也被称为方法区(method area),这个区域也可能发生GC,并且发生在这个区域上的GC事件也会被算为major GC
#新生代的构成
一个伊甸园空间(Eden)
两个幸存者空间(Survivor) 为了理解GC、首先要了解用于存放所有新建的实例对象的年轻代,年轻代被划分为三块:
a)一个Eden space(伊甸园,觉得还是不要直译的好、怪怪的)
b)两个Survivor spaces(幸存者空间,依然不再直译了)
总共三个空间:其中两个是Survivor spaces,每个空间中事件的执行步骤如下:注:目的就是在对象被转移到年老代之前、增加被回收几率。
从上述步骤可以看出、其中一个Survivor space一定是空的。如果你的两个Survivor space都有数据、或者都没有数据、那么很有可能意味着你的程序出了什么问题。绝大部分新建对象都会被分配到Eden space。
Edenspace执行第一次GC之后,继续存在的实例对象会被转移到Survivor space.
Edenspace再执行一次GC之后,继续存在的实例对象会被转移到那个已经有实例对象的Survivor space中。
一旦Survivor space满了之后、继续存在的对象会被转移到另一个Survivor space中。然后前面的Survivor space就会被清空。
上述步骤重复到一定次数之后仍然存活的实例对象就会被转移到年老代中。
在HotSpot VM中、有两种技术被应用与加快内存分配:
1.Bump-the-pointer
2.TLABs(Thread-Local Allocation Buffers).
Bump-the-pointer(指针)技术追踪最后一个添加到Eden space的实例对象的位置。此对象会被放置与Eden space顶部。当有再新对象被创建时、只需要检查最后一个存放在Eden space中的对象的信息就可以知道新建对象是否能够存放在Eden space。如果可以则会被存放在Edenspace顶部。所以当一个新的对象被创建之后、之后最后一个被存放的对象会被检测,这种机制大大提升了内存分配效率。然而这种方式却不适合多线程的环境下、多线程操作时会因为锁而导致某些线程不能正常执行、这就导致了效率大大降低。
TLABs则是针对这种情况设计的。TLABs允许每个线程把Eden space的一小部分做为自己的操作对象、这样一来、每个线程就可以使用Bump-top-pointer技术来加快内存分配,从而避免锁带来的线程问题。
新建的对象是存放在Eden space中的、经过各种处理依然存活的对象将会被转移到年老代中。
#老年代GC处理机制
当年老代中存放满实例对象时会触发"full GC"执行
Serial GC (-XX:+UseSerialGC) "mark-sweep-compact"(标记-擦除-压缩)
单线程执行回收操作,回收期间暂停所有应用线程的执行,client模式下的默认回收器
年轻代:使用的GC算法(bump-top-pointer,TLABs)。设计的选型为复制
年老代的回收分为三个步骤,标记(Mark)、清除(Sweep)、合并(Compact)。
标记阶段把所有存活的对象标记出来;
清除阶段释放所有死亡的对象;
合并阶段将所有存活对象在堆中从前到后连续排放、并且把堆空间划分成两部分、一部分存放对象、另一部分未存放对象(即压缩部分)。设计的选型为合并,减少内存的碎片
Parallel GC (-XX:+UseParallelGC)
使用多个线程同时进行垃圾回收,多核环境里面可以充分的利用CPU资源,减少回收时间,增加JVM生产率,Server模式下的默认回收器。与串行回收器相同,回收期间暂停所有应用线程的执行
年轻代:使用多个线程回收垃圾,每一个线程的算法与串行回收器相同
老年代:年老代依然是单线程的,与串行回收器相同
Parallel Old GC (Parallel Compacting GC)(-XX:+UseParallelOldGC)
年老代分为三个步骤,标记、统计(摘要)、合并。
标记阶段,把所有存活的对象划分为N组(应该与回收线程数相同),每一个线程独立的负责自己那一组,标记存活对象的位置以及 所在区(Region)的存活率信息,标记为并行的。
统计阶段,统计每一个区(Region)的存活率,原则上靠前面的存活率较高,从前到后, 找到值得合并的开始位置(绝大多数对象都存活的区不值得合并),统计阶段是串行的(单线程)。
合并阶段,依据统计阶段的信息,多线程 并行的把存活的对象从一个区(Region)复制到另外一个区(Region)。
这里用到分的思想,把年老代划分为很多个固定大小的区(region)。-XX:ParallelGCThreads=3还可进一步指定参与并行回收的线程数,与串行回收器相同,回收期间暂停所有应用线程的执行。与并行回收器相比,年老代的回收时间更短,从而减少了暂停时间间隔(Pause time) *
Parallel Old GC在JDK5之后出现。与parallel GC相比,唯一的区别在于针对老年代的GC算法。Parallel Old GC分为三步:标记-汇总-压缩(mark - summary - compaction)。汇总(summary)步骤与清理(sweep)的不同之处在于,其将依然幸存的对象分发到GC预先处理好的不同区域,算法相对清理来说略微复杂一点。
Concurrent Mark & Sweep GC (or "CMS") (-XX:+UseConcMarkSweepGC)
分为四个步骤,初始标记(Initial Mark)、并发标记(Concurrent Mark)、再次标记(Remark)、以及并发清理(Concurrent Sweep)。特别注意,没有合并操作,所以会有碎片。
初始化阶段: 暂停应用线程,找出所有存活的对象,只有靠近类加载器的存活对象会被标记,因此停顿时间(stop-the-world)比较短暂,回收器使用单线程。所有被幸存对象引用的对象会在并发标记阶段被确认是否已经被追踪和校验
并发标记阶段: 回收器标记操作与应用并发运行,回收器使用单线程标记存活对象。
再次标记:并发标记阶段由于应用程序也在运行,这个过程中可能新增或者修改对象。所以再次暂停应用线程,找出所有修改的对象,使用多线程标记。
并发清理:回收器清理操作与应用并发运行,回收器使用单线程清理死亡对象。
一旦采取了这种GC类型,由GC导致的暂停时间会极其短暂。CMS GC也被称为低延迟GC。它经常被用在那些对于响应时间要求十分苛刻的应用之上。
当然,这种GC类型在拥有stop-the-world时间很短的优点的同时,也有如下缺点:
1)它会比其他GC类型占用更多的内存和CPU
2)默认情况下不支持压缩步骤
使用CMS GC之前需要对系统做全面的分析。另外为了避免过多的内存碎片而需要执行压缩任务时,CMS GC会比任何其他GC带来更多的stop-the-world时间,所以你需要分析和判断压缩任务执行的频率及其耗时情况。
Garbage First (G1) GC
如果你想清晰的理解G1,请先忘记上面介绍的有关新生代和老年代的知识。如上图所示,每个对象在创建时会分析到一个格子中,后续的GC也是在格子中完成的。每当一个区域分配满对象后,新创建的对象就会分配到另外一个区域,并开始执行GC。这种GC中不会出现其他GC中的对象在新生代和老生代三区域中移动的现象。G1是为了取代在长期使用中暴露出大量问题且饱受抱怨的CMS GC。
G1最大的改进在于其性能表现,它比以上任何一种GC都更快速。
2、3、4类的年轻代的回收算法(Minor Collection)回收方法相同,所以上面只针对老年代做介绍
方法区:
存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用永久代(PermanetGeneration)来存放方法区,(在JDK的HotSpot虚拟机中,可以认为方法区就是永久代,但是在其他类型的虚拟机中,没有永久代的概念,有关信息可以看周志明的书)可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。
到此,关于"jvm GC怎么掌握"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!