JVM垃圾收集器
标签:JVM

垃圾收集器

1. 串行回收器

串行收集器仅仅使用单线程进行垃圾回收,它是独占式的垃圾回收。垃圾回收时,应用程序所有线程停止工作,进行等待,即Stop-The-World

使用-XX:UseSerialGC参数可以指定使用新生代串行收集器和老年代串行收集器,当虚拟机在Client模式下运行时,它是默认的垃圾收集器。

1.1 新生代串行回收器

新生代串行回收期采用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销。在单CPU的情况下,它的性能可以超过并行回收器。

1.2 老年代串行回收器

老年代串行回收器使用的是标记整理算法。老年代回收器比新生代回收占用的时间更长,因此程序可能停顿较长的时间。

2. 并行回收器

2.1 新生代ParNew回收器

ParNew工作在新生代,它只是简单的将串行回收器多线程化,它的回收策略、算法以及参数和新生代串行回收器一样。ParNew也是独占式回收器,存在停顿问题。在多核CPU中,多个线程同时回收,效率比串行回收器高,但在单CPU中,并行回收器不一定串行回收器好,反而可能比串行差。

ParNew回收器工作线程数量可以通过-XX:ParallelGCThreads参数指定,一般最好与CPU数量相当,过多的线程数,影响垃圾回收性能。在默认情况下,当CPU数小于8时,ParallelGCThreads的值等于CPU数量,当CPU数量大于8时,值等于3+(5*CPU_Count)/8

2.2 新生代ParallelGC回收器

ParallelGC采用的是复制算法、多线程、独占式,但是它更加关注系统的吞吐量

ParallelGC提供了两个参数用于控制系统吞吐量:

-XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间。他的值是一个大于0的整数。ParallelGC工作时,会调整堆大小和其他的参数来尽量把停顿时间控制在MaxGCPauseMillis内。如果希望减小停顿时间,可以把这个值设置得很小,为了达到预期的停顿时间,系统可能会使用一个很小的堆(一个小堆的回收比大堆回收快),但是这样会导致垃圾回收变得很频繁,从而增加了垃圾回收时总间,降低了吞吐量。

-XX:GCTimeRatio:设置吞吐量的大小,为0至100之间的整数。假设GCTimeRatio的值为n,那么系统将花费不超过1/(1+n)的时间用于垃圾收集,比如GCTimeRatio的值为99(默认值),系统将花费不超过1/(1+99)=1%的时间。

上面这两个参数是相互矛盾的,减少停顿时间,吞吐量也减少了;增大吞吐量,停顿时间也会增加。

此外,ParallelGC还支持自适应的GC调整策略,使用-XX:+UseAdaptiveSizePolicy可以打开自适应策略。在这种模式下,新生代的大小,eden和survivor的比例,晋升老年代的对象年龄等参数会被自动调整,以达到堆大小,吞吐量和停顿时间的平衡点。平时可以仅设定最大堆,和GCTimeRatioMaxGCPauseMillis,让虚拟机自己完成调优工作。

2.4 老年代ParallelOldGC回收器

老年代的ParallelOldGC和新生代的ParallelGC差不多,只不过它应用于老年代。

3. CMS回收器

CMS关注系统停顿时间,即Concurrent-Mark-Swap,采用的并发标记清除算法。

-XX:+UseConcMarkSweepGC:启动CMS

3.1 CMS工作主要步骤

除了初始标记重新标记是独占的以外,并发标记预处理并发清理并发重置都是并行的。

预清理可以通过开关-XX:-CMSPrecleaningEnabled关闭预清理。除了为正式清理做准备和检查以外,预清理尝试控制一次停顿时间。由于重新标记是独占CPU的,如果新生代GC发生后,立即触发一次重新标记,那么一次停顿时间很长。为了避免这种情况,预处理时,会等待一次新生代GC的发生,然后根据历史性能数据预测下一次新生代GC发生的时间,在这两次GC时间中间时刻进行重新标记。这样最大程度减少新生代GC和重新标记重合,尽可能减少停顿时间。

3.2 CMS主要参数设置

CMS的默认启动的并发线程数是(ParallelGCThreads+3)/4。ParallelGCThreads表示GC并行时使用的线程数,如果新生代使用ParNew,那么ParallelGCThreads就是新生代GC的线程数量。根据公式可以得出4个ParallelGCThreads,只有一个并发线程。5~8个是有2两个并发线程

并发线程数量可以通过-XX:ConcGCThreads或者-XX:ParallelCMSThreads参数手工设定。当CPU资源紧张时,在CMS回收阶段时,系统性能将会非常糟糕。

CMS不是独占的回收器,在回收过程中,应用程序也在不断的执行,所以要保证应用程序有足够的内存空间可以使用。因此CMS不会等到堆内存饱和时才进行垃圾回收,而是堆内存使用率达到一定的阈值,就会进行垃圾回收。这个阈值可以使用-XX:CMSInitiatingOccupancyFraction来指定,默认值是68。即老年代的空间使用率达到68%时就进行垃圾回收。

如果内存的使用率增长很快,那么在CMS执行过程中,已经出现了内存不足的情况。此时,CMS就会失败,虚拟机就会老年代串行收集器,这样程序将被中断,停顿时间变长。故内存增长慢时,可以设置一个较大的值,降低CMS触发频率,减少老年代回收次数可以明显提高性能。反之,内存使用率增长快时,降低这个值,避免触发老年代串行回收器。

CMS将会产生大量碎片,可以通过-XX:+UseCMSCompactAtFullCollection开关使CMS在垃圾收集完后,进行一次碎片整理,由于碎片整理不是并发的,可以通过-XX:CMSFullGCsBeforeCompaction指定多少次CMS后,进行碎片整理。

3.3 永久代回收

如果希望CMS回收Perm区,则必须打开-XX:+CMSClassUnloadingEnabled开关,如果条件允许的情况下,系统会使用CMS回收Perm区。

4. G1回收器

JDK1.7引入的,为了替代CMS回收器,**从分代上看:**G1依然属于分代回收器,它会区分老年代,新生代,依然有eden区和survivor区。**从堆结构上看:**它不要求整个eden区,新生代和老年代都连续的。

4.1 内划分和主要收集过程

G1将内存划分为一个一个的区域,每次收集只收集结果区域,来控制回收停顿时间。

主要有4个阶段:

  1. 新生代GC
  2. 并发标记周期
  3. 混合收集
  4. 如果需要,可能会进行Full GC

4.2 G1并发标记周期

  1. 初始标记:标记从根节点直接可达的对象,这个阶段会伴随一次新生代GC,它是会产生全局停顿的,应用程序在这个阶段必须停止执行。
  2. 根区域扫描:由于初始标记会伴随一次新生代GC,所以在初始化标记后,eden区被清空,并且存活对象被移入survivor区。这个阶段,将扫描由survivor区直接可达的老年代区域,并标记这些直接可达的对象。这个过程是可以和应用程序并发执行的,但是根区域扫描不能和新生代GC同时执行(因为根区域扫描依赖survivor区的对象,而新生代GC会修改这个区域)。因此如果这个时候恰巧要进行新生代GC,这次新生代GC的时间就会被延长,要等到根区域扫描结束。
  3. 并发标记:和CMS类似,并发标记将会扫描并查找整个堆的存活对象,并做好标记,这是一个并发的过程,并且这个过程可以被一次新生代GC打断。
  4. 重新标记:和CMS一样,重新标记会使应用程序产生停顿。由于并发标记过程中,应用程序依然在运行,因此标记结果可能需要进行修正,所以在此对上一次标记结果进行补充。在G1中,这个过程使用SATB(Snapshot-At-The-Beginning)算法完成,即在G1会在标记之初为存活对象创建一个快照,这个快照有助于加速重新标记的速度。
  5. 独占清理:这个阶段时会引起停顿的,它将计算各个区域的存活对象和GC回收比例并进行排序,识别可供混合回收的区域。在这个阶段,还会更新记忆集(Remebered Set)。该阶段给出了需要被混合回收的区域并进行标记,在混合回收阶段,需要这些信息。
  6. 并发清理阶段:这里会识别并清理完全空闲的区域,它是并发清理的,不会引起停顿。

4.3 混合回收

并发标记中,虽然有对象被回收,但总体上回收比例还是比较低的,但是在并发标记周期后,G1已经明确知道哪些区域含有较多的垃圾对象,在混合回收阶段,就可以专门针对这些区域进行回收。G1会优先回收垃圾比例区域较高的区域,因为回收这些的性价比也比较高

这个阶段叫混合回收阶段,因为既会执行正常的年轻代的GC,又会选取一些被标记的老年代区域进行回收。

混合GC会执行多次,直到回收到足够多的内存空间,然后,触发一次新生代GC。新生代GC后,又可能会发生一次并发标记周期的处理,最后,又会引起混合GC的执行。因此整个过程可能看起来:

4.4 必要时的Full GC

和CMS类似,并发收集由于应用程序和GC线程交替工作,因此总是不能完全避免在特别繁忙的场合会出现在内存回收的过程中内存不充足的情况。当遇到这种情况时,G1也会转入一个Full GC进行回收。

此外,如果在混合GC时发生内存空间不足或者在新生代GC时,survivor区和老年代无法容纳幸存对象,都会导致一次Full GC产生。

4.5 G1相关参数

-XX:+UseG1GC :打开G1收集器开关

-XX:MaxGCPauseMillis:它用于指定最大目标最大停顿时间,如果任何一次停顿超过这个设置值时,G1就会尝试重新调整新生代和老年代的比例,调整堆的大小,调整晋升老年代年龄等手段,试图达到预设目标。(但如果停顿时间缩短,新生代的GC次数可能要增加,GC反而变得更频繁,对于老年代来说,为了获得更短的停顿时间,那么混合GC收集时,一次收集的区域数量也会变少,这样无疑增加了Full GC的可能性)

-XX:ParallelGCThreads:设置并行回收时,GC的工作线程数量

-XX:InitiatingHeapOccupancyPercent:可以指定当堆使用率达到多少时,触发并发标记周期的执行。默认值是45,当堆的使用率达到45%时,执行并发标记周期。InitatingHeapOccupancyPercent一旦设置,始终都不会被G1收集器修改,这意味着G1收集器不会试图修改这个值,来满足MaxGCPauseMillis的目标。 如果这个值设置的过大,会到时并发周期迟迟得不到启动,那么引起Full GC的可能性也会增加。反之,一个较小的值,会使得并发周期启动频繁,大量的GC线程抢占CPU,导致程序效率降低。

  • 12 min read

CONTRIBUTORS


  • 12 min read