首页 > Java > 正文

JVM GC原理与调优(下)

标签:java, gc

(接上篇)
G1深度原理
G1把整个Java堆划分为若干个区间(Regions)。每个Region大小为2的倍数,范围在1MB-32MB之间,可能为1,2,4,8,16,32MB。所有的Region有一样的大小,JVM生命周期内不会改变。例如-Xmx16g –Xms16g,设置16GB的堆大小,2000个Regions,则每个Region=16GB/2000=8MB。如果堆大小很大,而每个Region的大小很小,则Region数量可能会超过2000个。同样地,很小的堆大小会导致Region数量很少。

每个线程都有自己的Processed Buffer。每记录满一个Buffer就把它放到全局的列表里,再申请一个新的Buffer继续写。全局的Buffer逻辑上划分为4个zone:
(1) White Zone 什么都不做
(2) Green Zone(XX:G1ConcRefinementGreenZone) 启动Refinement threads,处理Buffer
(3) Yellow Zone(XX:G1ConcRefinementYellowZone) 所有的Refinement threads都启动
(4) Red Zone(XX:G1ConcRefinementRedZone) 应用线程也用来处理

 基于老年代的收集器采用Heap里不同区域区分/隔离对象,这些不同的区域里面的对象对应了不同年代。这样年代收集器可以集中精力在最近分配的对象上,因为它们会发现一些对象不久会死亡。这些年代在堆里可以被分别收集,这样不用扫描整个Heap,可以节省时间和减小响应时间,并且存活时间长的对象不用来回复制,减少了拷贝和引用更新开销。
为了方便收集器的独立性,许多GC维持了每个年代的RSet。每一个RSet是一个数据结构,它维护并跟踪收集器单元的内部引用,如G1 GC的Region一样,减少了扫描整个Heap堆获取信息的耗时。当G1 GC执行了一个Stop-the-world收集(年轻代或混合代),它可以通过CSet扫描Region的RSets。一旦存活对象被移除,它们的引用立即被更新。
随着很多对象被提升到老年代,以及大对象进入大对象区间,整个Java堆区占有率上升。为了避免Java堆空间溢出,JVM进程需要去初始化一个GC(不仅包含年轻代Regions,也包含增加老年代Region到混合收集器)。在混合GC事件里,所有的年轻代Regions会被收集,同时一部分老年代Region也会被收集。
G1常用参数
我在这里所罗列的参数的默认值都是基于JDK8u45,所以可能后续的JDK版本会有些值不一样,这个读者可以直接通过JDK的官方帮助文档获取最新默认值信息。

-XX:+UseG1GC:启用G1 GC。JDK7和JDK8要求必须显示申请启动G1 GC,JDK可能会设置G1 GC为默认GC选项,也有可能会退到早期的Parallel GC,这个也请关注吧,JDK9预计2017年正式发布;

-XX:G1NewSizePercent:初始年轻代占整个Java Heap的大小,默认值为5%;

-XX:G1MaxNewSizePercent:最大年轻代占整个Java Heap的大小,默认值为60%;

-XX:G1HeapRegionSize:设置每个Region的大小,单位MB,需要为1,2,4,8,16,32其一,默认是堆内存的1/2000。前面我们讲过大对象概念,如果这个值设置比较大,那么大对象就可以进入Region了,同样地,这样做的坏处是直接干预了各年龄代的分配大小;

-XX:ConcGCThreads:与Java应用一起执行的GC线程数量。默认是Java线程的1/4。减少这个参数的数值可能会提升并行回收的效率,即提高系统内部吞吐量(系统是一个整体,CPU资源大家都需要占用),不过如果这个数值过低,也会导致并行回收机制耗时加长;

-XX:+InitiatingHeapOccupancyPercent(简称IHOP):G1内部并行循环启动的设置值,默认为Java Heap的45%。这个可以理解为老年代空间占用的空间,GC收集后需要低于45%的占用率。这个值主要是为了决定在什么时间启动老年代的并行回收循环,这个循环从初始化并行回收开始,可以避免Full GC的发生;

-XX:G1HeapWastePercent:G1不会回收的内存大小,默认是堆大小的5%。GC会收集所有的Region,如果值达到5%,就会停下来不再收集了; -XX:G1MixedGCCountTarget:设置并行循环之后需要有多少个混合GC启动,默认值是8个。老年代Regions的回收时间通常比年轻代的收集时间要长一些,所以如果混合收集器比较多,可以允许G1延长老年代的收集时间;

-XX:+G1PrintRegionLivenessInfo:这个参数需要和-XX:+UnlockDiagnosticVMOptions配合启动,这可以理解,它们本身就是属于VM的调试信息。如果开启了,VM会打印堆内存里每个Region的存活对象信息。这个信息在标记循环结束后可以打印出来;

-XX:G1ReservePercent:这个值是为了保留一些空间用于年代之间的提升,默认值是堆空间的10%。注意这个空间保留后就不会用在年轻代了,大家可以看到GC日志里输出显示,我们大量执行的是年轻代回收,所以如果你的应用里面有比较大的堆内存空间、比较多的大对象存活,那还是减少一点保留空间吧,这样会给年轻代更多的预留空间、GC之间更长的处理时间;

-XX:+G1SummarizeRSetStats:这个也是一个VM的调试信息。如果启用,会在VM推出的时候打印出RSets的详细总结信息。如果启用-XX:G1SummaryRSetStatsPeriod参数,就会阶段性地打印RSets信息;

-XX:+G1TraceConcRefinement:这个也是一个VM的调试信息。如果启用,并行回收阶段的日志就会被详细打印出来;

-XX:+GCTimeRatio:大家知道,GC的有些阶段是需要Stop-the-World,即停止应用线程的,这个参数就是计算花在Java应用线程上和花在GC线程上的时间比率,默认是9。这个参数主要的目的是让用户可以控制花在应用上的时间,G1的计算公式是100/(1+GCTimeRatio),这样如果采用9,则最多10%的时间会花在GC工作上面。Parallel GC的默认值是99,表示1%的时间被用在GC上面,这是因为Parallel GC贯穿整个GC,而G1则根据Region来进行划分,不需要全局性扫描Java Heap;

-XX:+UseStringDeduplication:手动开启Java String对象的分割工作,这个是JDK8u20之后新增的参数,主要用于相同String避免重复申请内存,节约Region的使用;

-XX:MaxGCPauseMills:G1停止执行的一个目标值,单位是毫秒,默认是200毫秒,这个值不一定真的会达到。这个参数会通过控制年轻代的大小来实现目标。
总结
垃圾回收机制一直是Java着力加强点,G1 GC通过引入许多不同的方式解决了Parallel、Serial、CMS GC的许多缺点。G1通过将Heap划分为多个Region,可以让G1操作可以在一个Region里面执行而不是整个Java堆或者整个年代(Generation)。几乎所有的应用程序都有自身的独特性,所以不能给出千篇一律的GC配置单,需要的是读者自己的反复试验、比对结果、确定方案。


原创文章,转载请注明出处!
本文链接:http://wuukee.github.io/posts/gc1.html
上篇: JVM GC原理与调优(上)
下篇: 2018北京马拉松小记