前言
前面两篇主要整理了性能测试的主要观察指标信息:性能测试篇,以及JVM性能调优的工具:JVM篇。这一篇先简单总结一下GC的种类,然后侧重总结下G1(Garbage-First)垃圾收集器的分代,结合open-jdk源码分析下重要算法如SATB,重要存储结构如CSet、RSet、TLAB、PLAB、Card Table等。最后会再梳理下G1 GC的YoungGC,MixedGC收集过程。
GC的分类
GC的主要回收区域就是年轻代(young gen)、老年代(tenured gen)、持久区(perm gen)。在jdk8之后,perm gen消失,被替换成了元空间(Metaspace)。垃圾收集为了提高效率,采用分代收集的方式,对于不同特点的回收区域使用不同的垃圾收集器。系统正常运行情况young是比较频繁的,full gc会触发整个heap的扫描和回收。在G1垃圾收集器中,最好的优化状态就是通过不断调整分区空间,避免进行full gc,可以大幅提高吞吐量。下面会详细介绍。
串行垃圾回收器
JDK 1.3之前的垃圾回收器,单线程回收,并且会有stop theworld(下文会简称STW),也即GC时,暂停所有用户线程。其运行方式是单线程的,适合Client模式的应用,适合单CPU环境。串行的垃圾收集器有两种,Serial以及SerialOld,一般会搭配使用。新生代使用Serial采取复制算法,老年代使用Serial Old采取标记整理算法。Client应用或者命令行程序可以,通过**-XX:+UseSerialGC**可以开启上述回收模式。
- Serial:用于新生代垃圾收集,复制算法
- SerialOld:用于老年代垃圾收集,标记整理算法
并行垃圾回收器
整体来说,并行垃圾回收相对于串行,是通过多线程运行垃圾收集的。也会stop-the-world。适合Server模式以及多CPU环境。一般会和jdk1.5之后出现的CMS搭配使用。并行的垃圾回收器有以下几种:
- ParNew:Serial收集器的多线程版本,默认开启的收集线程数和cpu数量一样,运行数量可以通过修改ParallelGCThreads设定。用于新生代收集,复制算法。使用**-XX:+UseParNewGC**,和Serial Old收集器组合进行内存回收。
- Parallel Scavenge: 关注吞吐量,吞吐量优先,吞吐量=代码运行时间/(代码运行时间+垃圾收集时间),也就是高效率利用cpu时间,尽快完成程序的运算任务
可以设置最大停顿时间MaxGCPauseMillis以及,吞吐量大小GCTimeRatio。如果设置了-XX:+UseAdaptiveSizePolicy参数,则随着GC,会动态调整新生代的大小,Eden,Survivor比例等,以提供最合适的停顿时间或者最大的吞吐量。用于新生代收集,复制算法。通过**-XX:+UseParallelGC**参数,Server模式下默认提供了其和SerialOld进行搭配的分代收集方式。 - Parllel Old:Parallel Scavenge的老年代版本。JDK 1.6开始提供的。在此之前Parallel Scavenge的地位也很尴尬,而有了Parllel Old之后,通过**-XX:+UseParallelOldGC**参数使用Parallel Scavenge + Parallel Old器组合进行内存回收。
并发标记扫描垃圾回收器(CMS)
CMS(Concurrent Mark Sweep)基于“标记—清除”算法,用于老年代,所以其关注点在于减少“pause time”也即因垃圾回收导致的stop the world时间。对于重视服务的响应速度的应用可以使用CMS。因为CMS是“并发”运行的,也即垃圾收集线程可以和用户线程同时运行。 缺点就是会产生内存碎片。
CMS的回收分为几个阶段:
- 初始标记:标记一下GC Roots能直接关联到的对象,会“Stop The World”
- 并发标记:GC Roots Tracing,可以和用户线程并发执行。
- 重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,会“Stop The World”。
- 并发清除:清除对象,可以和用户线程并发执行。
CMS最主要解决了pause time,但是会占用CPU资源,牺牲吞吐量。CMS默认启动的回收线程数是(CPU数量+3)/ 4,当CPU<4个时,会影响用户线程的执行。另外一个缺点就是内存碎片的问题了,碎片会给大对象的内存分配造成麻烦,如果老年代的可用的连续空间也无法分配时,会触发full gc。并且full gc时如果发生young gc会被young gc打断,执行完young gc之后再继续执行full gc。
-XX:UseConcMarkSweepGC参数可以开启CMS,年轻代使用ParNew,老年代使用CMS,同时Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用。
G1垃圾收集器
G1(Garbage-First)是在JDK 7u4版本之后发布的垃圾收集器,并在jdk9中成为默认垃圾收集器。通过“-XX:+UseG1GC”启动参数即可指定使用G1 GC。从整体来说,G1也是利用多CPU来缩短stop the world时间,并且是高效的并发垃圾收集器。但是G1不再像上文所述的垃圾收集器,需要分代配合不同的垃圾收集器,因为G1中的垃圾收集区域是“分区”(Region)的。G1的分代收集和以上垃圾收集器不同的就是除了有年轻代的ygc,全堆扫描的fullgc外,还有包含所有年轻代以及部分老年代Region的MixedGC。G1的优势还有可以通过调整参数,指定垃圾收集的最大允许pause time。下面会详细阐述下G1分区以及分代的概念,以及G1 GC的几种收集过程的分类。
G1分区的概念
在G1之前的