G1 的 JVM 参数解析
为了更好地使用 G1,一起来看看 G1 中有哪些重要的 JVM 参数需要配置,以及如何配置。
-XX:+UseG1GC
这个参数告诉 JVM,我们计划使用 G1 作为垃圾回收器,所以它是使用 G1 的必设参数。
-XX:G1HeapRegionSize
G1 垃圾回收器用这个参数来设置每个 Region 的大小。在不设定此参数的情况下,JVM 会根据整个堆的大小自动设定 Region 大小,JVM 尝试为 G1 创建约 2048 个 Region。
在选择合适的 G1HeapRegionSize 值时,下面 2 点需要考虑。
- 首先是堆的大小,堆越大,每个 Region 的大小就越大。
- 其次是暂停时间的要求,如果需要更短的 GC 暂停时间,可以考虑把 Region 设小一些。最后就是对象的大小:如果应用有很多大对象,Region 就应该设置得大一些。
所以,如果你的应用分配的堆比较大,并且需要创建大量大对象的时候,可以考虑适当把这个值调大。而如果你的应用对暂停时间有很高的要求,比如金融交易系统,那么可以适当调低这个值,使我们的堆利用率和 GC 的暂停时间达到良好的平衡。G1HeapRegionSize 值对 G1 非常关键,在设置之前最好结合具体的业务和负载情况,根据监控和性能测试来动态调整和优化。
-XX:MaxGCPauseMillis
这是 G1 收集器的目标停顿时间,单位为毫秒,默认值为 200。JVM 将尽其所能不超过这个设置值。G1 会尽量达成,即使达不成,也会通过逐渐调整虚拟机的状态来尽力达成。比如,对于 Young GC 来说,就是通过减少 Eden space 的个数,来降低 Eden space 的处理时间。
减少 Eden 的总空间时,会引发更加频繁地 Young GC,也会同步加快 Mixed GC 的执行频率,因为 Mixed GC 是由 Young GC 触发的,或者说是借机同时执行的。频繁 GC 会对应用的吞吐量造成影响,每次 Mixed GC 回收时间太短,回收的垃圾量太少,可能导致 GC 的垃圾清理速度赶不上应用产生的速度,从而引发 Full GC,这是要极力避免的。
所以暂停时间不是设置的越小越好,也不能设置得偏大却指望 G1 自己会尽快处理。这样可能会导致一次全部并发标记后触发的 Mixed GC 次数变少,但每次的时间变长,STW(stop the word)时间变长,这样对应用的影响更加明显。
-XX:InitiatingHeapOccupancyPercent
这个参数简称 IHOP,是 G1 通过整个堆占用情况来决定是否需要对老年代启动并发标记的阈值。默认值是 45,也就是当整个堆空间有 45% 的空间被占用时,JVM 会启动并发标记。
使用 G1 时,对这个值的设置非常重要。如果我们设置得过高,将导致并发标记的启动过于迟缓,可能没有完成标记就需要进行 Full GC。而设置过低的话,会导致过于频繁地并发标记,增加 CPU 的负载,从而影响整个系统的性能。在实际的业务场景中,如果你的应用对象创建并存活到老年代的速度比较快,或者并发标记所需的时间比较长,我们可以通过降低 IHOP 的值来避免 FULL GC。
而如果你的业务场景中,并发标记完成得很快,或者对象生产的速度不高,我们可以通过提高 IHOP 的值,减少并发标记的次数,从而减少 STW 的次数提升整个系统的性能。具体实践中,你可以先通过日志查看历次 Young GC 的信息,观察每次 Young GC 后,老年代的使用率,取一个业务高峰期老年代的平均使用率作为 IHOP 的初始值,然后通过业务压力测试、调优,动态调整这个值,来达到最佳的效果。
从 JDK 9 开始,IHOP 的默认值虽然也被设定为 45,但是 G1 会根据运行情况动态调节 IHOP 的值,当然你也可以通过 -XX:-G1UseAdaptiveIHOP参数来关闭这个功能。
-XX:ParallelGCThreads
这个参数设置了 GC 的线程数,一般情况下,可以设置为和 CPU 内核数相同的数字。
-XX:ConcGCThreads
这个参数设置并行 GC 的线程数,默认值是 ParallelGCThreads 除以 4 的值,也就是在非 STW 期间的 GC 工作线程数,当然其他的线程很多工作在应用上。当并发周期时间过长时,可以尝试调大 GC 工作线程数,但是这也意味着这期间应用所占的线程数减少,会对吞吐量有一定影响,可以设置为 ConcGCThreads=(3+ParallelGCThreads)÷4。
总结
通过将堆内存化整为零,G1 以 Region 作为最小的内存分配和回收的单元,从而有效减少了 GC 的范围,并通过引入 global concurrent marking 机制、建立预期停顿模型,实现对内存回收的精细化的管理,从而具备了“低延迟”和“可预测”的能力。
由于 G1 在垃圾回收过程中,采用的是标记复制的算法而不是标记清除,所以垃圾回收中的空间碎片化问题,在 G1 中得到了有效的解决。当然这些优势的背后,也增加了一些潜在的成本,由于采用 Region 作为最小内存管理单元,而且 G1 为每一个 Region 都建立了一个独立的 Remembered Set,当程序中对象创建或变动频繁的时候,G1 的管理成本相比其他垃圾回收器必然会增高。
而且 G1 也不是银弹,使用不当,依然可能引发 Full GC。尤其是在使用 JDK 8 的时候,到底是选择 CMS 还是 G1,还是需要基于程序的特性,比如对象生成速度以及分配给 JVM 的内存,需要你结合实际的压测数据进行综合的调优和慎重的选择。
最后你在使用和配置 G1 的 JVM 参数的时候,还需要重点关注几个方面。
- 首先是选择合适的堆大小,保证系统有足够的内存供应用程序运行,但也要避免过大导致长时间的 GC。
- 其次选择合理的目标停顿时间,通过定制 GC 停顿时间,实现吞吐量和停顿时间的平衡。
- 线程数和 CPU 核心数一致也要重点关注,选择合适的并行线程数可以有效利用硬件资源,提高 GC 效率。
- 合理设置 Region 的大小,避免过大或者过小,从而影响整体 GC 的性能。
此文章为9月Day22学习笔记,内容来源于极客时间《云时代JVM实战 》,强烈推荐该课程
网友评论