Java虚拟机规范并没有规定垃圾收集器应该如何实现,因此不同厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大差别。一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。
jdk7的HotSpot虚拟机提供7种垃圾收集器:
负责新生代垃圾收集的Serial、ParNew、Parallel Scavenge
负责老年代垃圾收集的Serial Old、Parallel Old、CMS
既负责新生代,又负责老年代的G1
新生代收集器
1、Serial收集器 -XX:+UseSerialGC
Serial是串行的意思。在jdk1.3之前,新生代只有Serial收集器可用。它采用复制算法,是一个单线程收集器,只会使用一个CPU或一个线程去完成垃圾收集工作。对于单CPU的环境来说,Serial收集器由于没有线程切换,所以有很高的收集效率。Serial收集器在垃圾收集时,必须暂停其他所有工作线程,直到收集结束为止。这种操作称为Stop The World,简称STW。这种把用户正常工作的线程全部停掉的操作,对很多应用来说是难以接受的。
Serial收集器是HotSpot虚拟机运行在Client模式下默认的垃圾收集器。在桌面应用场景中,分配给虚拟机的内存一般不会很大,收集几十甚至一两百M新生代内存,STW时间可以控制在100ms以内,只要不频繁发生,这点时间可以接受。所以,Serial收集器对于运行在Client模式的虚拟机来说是一个很好的选择。
2、ParNew收集器 -XX:+UseParNewGC
ParNew收集器是Serial收集器的多线程版本。除了使用多线程并行垃圾收集外,它与Serial收集器完全相同,如也是采用复制算法,也会STW等等。
ParNew收集器默认开启的收集线程数,当CPU数小于8时,等于CPU数。当CPU数大于8时,等于3+(5*CPU_COUNT/8)。在CPU非常多的情况下,可使用-XX:ParallelGCThreads指定线程数。如-XX:ParallelGCThreads=16
3、Parallel Scavenge收集器 -XX:+UseParallelGC
Parallel Scavenge收集器也是一个多线程并行的新生代垃圾收集器。它也采用复制算法,也有STW。Parallel Scavenge收集器的目标是大吞吐量,所以也被称为吞吐量优先收集器。吞吐量计算公式是:运行用户程序时间/(运行用户程序时间+垃圾收集时间),是个比例值,值越大,代表吞吐量越高。
重要参数有3个:
1)-XX:MaxGCPauseMillis,最大垃圾收集停顿时间。值是一个大于0的毫秒数,收集器将尽量保证垃圾收集的时间不超过此值。值不宜过小,因为有可能还会造成吞吐量的降低。
2)-XX:GCTimeRatio,用户代码运行时间与垃圾收集时间的比值。值是一个(0,100)的整数,默认是99。即99%时间运行用户代码,此时吞吐量是99%。如果设值为5,则5/6的时间运行用户代码,吞吐量是83.3%。
3)-XX:+UseAdaptiveSizePolicy,这是一个开关参数,设置后,就不需要手工指定新生代的大小(-Xmn)、Eden和Survivor的比例(-XX:SurvivorRatio)等细节参数了,虚拟机会根据当前系统的运行情况动态调整这些参数,以求最大吞吐量。这被称为GC自适应调节策略(GC Ergonomics)。自适应调节策略是Parallel Scavenge收集器与ParNew收集器的一个重要区别。
需要特别注意的是,Parallel Scavenge收集器不能与CMS收集器共用,Serial和ParNew可以。
老年代收集器
1、Serial Old收集器
Serial Old收集器是Serial收集器的老年代版本。同样是一个单线程收集器,但与Serial收集器不同,Serial Old采用的是标记整理算法。mark-compact。此收集器的主要意义也是给Client模式的虚拟机使用。在Server模式下,它还可以作为CMS收集器的后备方案,在并发收集发生Concurrent Mode Failure时使用。Serial Old和Serial、ParNew、Parallel Scavenge都可以共用。
2、Parallel Old收集器 -XX:+UseParallelOldGC
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。在jdk1.6中发布。同样是一个多线程收集器,但与Parallel Scavenge收集器不同,Parallel Old采用的是标记整理算法。在注重吞吐量以及CPU资源敏感的场合,可以优先考虑组合使用Parallel Scavenge收集器和Parallel Old收集器。
3、CMS收集器 -XX:+UseConcMarkSweepGC
CMS,是Concurrent Mark Sweep的简称。在jdk1.5中发布。从名字就可以看出,它是基于标记清除算法实现的。CMS收集器是一种以获取最短回收停顿时间为目标的收集器,它非常适合B/S系统服务端应用,这些应用都非常重视服务的响应速度。
CMS收集器工作流程可以分为4个步骤:
1)initial mark 初始标记,仅仅标记GC Roots能直接关联的对象,会Stop The World,但速度很快。
2)concurrent mark 并发标记,进行GC Roots Tracing的过程,在整个过程中耗时最多。
3)remark 重新标记,修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。这个阶段也会STW,时间会比初始标记长一些,但远比并发标记短得多。
4)concurrent sweep 并发清除。
由于整个过程中耗时最长的并发标记和并发清除,用户线程都可以和收集器线程一起工作,所以从整体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
CMS收集器的优点:
并发收集、低停顿,所以CMS收集器也被称为并发低停顿收集器Concurrent Low Pause Collector。
缺点:
1)对CPU资源非常敏感。CMS默认启动的回收线程数是(CPU数量+3)/4,当CPU数量小于4时,对用户程序的影响很大。可以通过-XX:ParallelCMSThreads来设置线程数量。
2)无法处理浮动垃圾。浮动垃圾指的是在并发清除阶段,用户程序产生的垃圾。由于这些垃圾产生于标记之后,所以本次GC无法清除。可能出现Concurrent mode failure而导致下一次Full GC。
3)标记清除算法会产生大量空间碎片。这是由算法本身决定的。空间碎片过多时,无法给大对象分配大量连续空间,会导致下一次Full GC。
老年代收集器
G1收集器 -XX:+UseG1GC
G1其实是Garbage first的简称。G1收集器是一款面向服务端应用的收集器,在jdk1.7发布,是目前最前沿的收集器。
G1把内存分成若干个大小相同的region,每个region拥有各自的分代属性。每个region都维护一个Remembered Set,用于记录对象引用的情况。GC时,根据Remembered Set的引用情况去搜索。
整体执行流程是:
1)initial mark 初始标记,标记GC Roots能直接关联的对象。会Stop The World。
2)concurrent mark 并发标记,从GC Roots开始对堆中对象进行可达性分析。此阶段耗时较长,但可与用户程序并发执行。
3)final mark 最终标记,标记在并发标记过程中产生的垃圾。会Stop The World。
4)live data counting and evacuation 筛选回收。根据GC模式回收垃圾。
G1收集器的优势:
1)可以同时回收新生代和老年代。
2)并行性。回收期间,可以多个线程同时工作,有效利用多CPU资源。
3)空间整理。回收过程中,会进行适当对象移动,减少空间碎片。
4)G1有停顿预测模型。我们可以用-XX:MaxGCPauseMillis指定期望的停顿时间,默认值是200ms。G1利用停顿预测模型,调整region数量,来尽量满足停顿时间。这个停顿时间指的就是STW的时间。
kafka(2.2.0)自带zookeeper启动后,通过jps -mlv命令,可看出jvm参数如下:
-Xmx512M -Xms512M -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -Xloggc:/logs/zookeeper-gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M
可以看出zookeeper用的是G1收集器,期望最大停顿时间是20ms,在堆使用率达到35%时触发GC。
kafka启动后,jvm参数如下:
-Xmx1G -Xms1G -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -Xloggc:/logs/kafkaServer-gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M
kafka2.2.0用的也是G1收集器,期望最大停顿时间是20ms,在堆使用率达到35%时触发GC。
G1调优:参考https://www.oracle.com/technical-resources/articles/java/g1gc.html
可以指定新生代的最小最大,但不要固定死,否则,期望最大停顿时间的设置会无效。