垃圾收集器总结

如何选择垃圾收集器

一般来说,选择使用哪款垃圾收集器可以从三个方面去考虑:

首先是你的应用系统所关注的主要矛盾点是什么?比如,某个项目是一个数据分析类的应用系统,我们更加希望它能够尽快地获得执行结果,那么吞吐量就是主要矛盾点,可以考虑使用 Parallel Scavenge。而假设某个应用它是一个 Web 应用,Stop The World 可能会直接影响服务质量,甚至可能会导致调用超时、业务直接失败等等严重的后果,那么低延迟就是我们的主要矛盾点,可以考虑使用 CMS 或者是 G1,将来 Shenandoah、ZGC 发展成熟之后,也可以使用这两款垃圾收集器。再比如某个应用,它是一个桌面端的应用,目前它最大的问题是启动非常慢,如果我们只是希望它能够启动得快一点,那么就可以考虑使用 Serial。另外前面还提过,可以使用这个参数:-Xverify:none 关闭掉校验的过程,也可以进一步地提升启动速度。总而言之,你应该找到自己应用程序最关注的矛盾点,并去结合前面介绍每款垃圾收集器的时候所总结的适用场景去合理的选择。

第二,选择哪款垃圾收集器和你的基础设施也有很大的关系。比如你的应用运行在一个单核的嵌入式机器上,那么使用 Parallel Scavenge、Parallel Old 等等并行收集器,或者是 CMS、G1 这种并发收集器可能都不是特别合适。又比如你所用的操作系统是 Windows,并且使用的是 JDK 11,那么就没有办法使用 ZGC,因为 JDK 14 才能支持 Windows 操作系统。

第三、选择使用哪款垃圾收集器和你的项目所使用的 JDK 也有很大的关系。比如你的项目使用的是 JDK 6,那么就没有办法使用 G1 这款垃圾收集器了。又比如你用的是 Orecle  JDK,那么就没有办法使用 Shenandoah。

总而言之,垃圾收集器的选择要考虑的因素非常多,这里都是从理论出发的一个分析,实际项目中千万不能纸上谈兵,凭想象力去选择,还是要结合实际的测试结果去选择。在实战章节我也会手把手地带你进行测试以及选择垃圾收集器。

垃圾收集器参数总结

下面看一下各款垃圾收集器相关的 JVM 参数,如下表所示: 收集器 | 参数及默认值 | 备注 | | ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Serial |  -XX:+UseSerialGC | 虚拟机在 Client 模式下的默认值,开启后,使用S erial + Serial Old 收集器的组合。 | | ParNew | -XX:UseParNewGC | 开启此参数使用 ParNew + serial old 收集器组合(不推荐)。 | |   | -XX:ParallelGCThreads=n | 设置垃圾收集器在并行阶段使用的垃圾收集线程数,当逻辑处理器数量小于8 时,n 的值与逻辑处理器数量相同;如果逻辑处理器数量大于 8 个,则 n 的值大约为逻辑处理器数量的 5/8,大多数情况下是这样,除了较大的 SPARC 系统,其中n的值约为逻辑处理器的 5/16。 | | Parallel Scavenge | -XX:UseParallelGC | 虚拟机在 Server 模式下的默认值,开启后,使用 Parallel Scavenge +Serial Old 收集器的组合。 | |   | -XX:MaxGCPauseMillis=n | 收集器尽可能保证单次内存回收停顿的时间不超过这个值,但是并不保证不超过该值。 | |   | -XX:GCTimeRatio=n | 设置系统的吞吐量,取值范围是 0~ 100,比如 GCTimeRatio 的值设为 99,则 GC 时间比为 1/(1+99)=1%,也就是要求吞吐量为 99%。若无法满足会缩小新生代大小。 | |   | -XX:+UseAdaptiveSizePolicy  | 开启后,无需人工指定新生代的大小(-Xmn) 、Ede n和 Survisor的比例(-XX:SurvivorRatio)以及晋升老年代对象的年龄(-XX:PretenureSizeThreshold)等参数,收集器会根据当前系统的运行情况自动调整。 | | Serial Old | 无 | Serial Old 是 Serial 的老年代版本,主要用于 Client 模式下的老生代收集,同时也是 CMS 在发生 ConcurrentMode Failure 时的后备方案。 | | Parallel Old | -XX:UseParallelOldGC | 开启后,使用 Parallel Scavenge +Parallel Old 收集器的组合。Parallel Old 是 Parallel Scavenge 的老年代版本,在注重吞吐量和 CPU 资源敏感的场合,可以优先考虑这个组合,该参数在JDK1.5 之后已无用。  | | CMS | -XX:+UseConcMarkSweepGc | 开启后,使用 ParNew + CMS 收集器的组合,Serial Old 收集器将作为CMS 收集器出现Concurrent Mode Failure 失败后的后备收集器使用。 | |   | -XX:CMSInitiatingOccupancyFraction=68 | CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认68% | |   | -XX:+UseCMSCompactAtFullCollection | 在完成垃圾收集后是否要进行一次内存碎片整理,默认开启。 | |   | -XX:CMSFullGCsBeforeCompaction=0 | 在进行若干次 Full GC 后就进行一次内存碎片整理,默认 0。 | |   | -XX:+UseCMSInitiatingOccupancyOnly | 允许使用占用值作为启动 CMS 收集器的唯一标准,一般和CMSFullGCsBeforeCompaction 配合使用。如果开启,那么当CMSFullGCsBeforeCompaction 达到阈值就开始 GC,如果关闭,那么 JVM 仅在第一次使用 CMSFullGCsBeforeCompaction 的值,后续则自动调整,默认关闭。 | |   | -XX:+CMSParallelRemarkEnabled | 重新标记阶段并行执行,使用此参数可降低标记停顿,默认打开(仅适用于 ParNewGC )。 | |   | -XX:+CMSScavengeBeforeRemark  | 开启或关闭在 CMS 重新标记阶段之前的清除(YGC)尝试。新生代里一部分对象会作为 GC Roots,让 CMS 在重新标记之前,做一次 YGC,而 YGC 能够回收掉新生代里大多数对象,这样就可以减少 GC Roots 的开销。因此,打开此开关,可在一定程度上降低 CMS 重新标记阶段的扫描时间,当然,开启此开关后,YGC 也会消耗一些时间。注意:开启此开关并不保证在标记阶段前一定会进行清除操作,生产环境建议开启,默认关闭。  | | CMS-Precleaning | -XX:+CMSPrecleaningEnabled | 是否启用并发预清理,默认开启。 | | CMS -AbortablePreclean | -XX:CMSscheduleRemark EdenSizeThreshold=2M | 如果伊甸园的内存使用超过该值,才可能进入“并发可中止的预清理”这个阶段 。 | |    | -XX:CMSMaxAbortablePrecleanLoops=o | “并发可终止的预清理阶段"的循环次数,默认0,表示不做限制 | |   | -XX:+CMSMaxAbortablePrecleanTime=5000 | “并发可终止的预清理"阶段持续的最大时间。 | |   | -XX:+CMSClassUnloadingEnabled | 使用CMS时,是否启用类卸载,默认开启。  | |   | -XX:+ExplicitGClnvokesConcurrent  | 显示调用 System.gc() 会触发 Full GC,会有 Stop The World,开启此参数后,可让 System.gc() 触发的垃圾回收变成一次普通的 CMS GC。 | |   | -XX:+UseG1GC | 使用G1收集器。 | |   | -XX:G1HeapRegionSIze=n | 设置每个 Region 的大小,该值为 2 的幂,范围为1MB 到 32MB,如不指定 G1会根据堆的大小自动决定 | |   | -XX:MaxGCPauseMillis=200 | 设置最大停顿时间.默认值为 200 毫秒。 | |   | -XX;G1NewSizePercent=5 | 设置年轻代占整个堆的最小百分比,默认值是 5,这是个实验参数。需用-XX:+UnlockExperimentalVMOptions 解锁试验参数后,才能使用该参数。 | |   | -XX:G1MaxNewSizePercenL=60 | 设置年轻代占整个堆的最大百分比,默认值是 60,这是个实验参数。需用 -XX:+unlockExperimentalVMOpticrs 解钺试验参数后,才能使用该参数。 | |   | -XX:ParallelGCThreads=n  | 设置垃圾收集器在并行阶段使用的垃圾收集线程数,当逻辑处理器数量小于8 时,n 的值与逻辑处理器数型相同;如果逻辑处理器数风汰于 8 个,则 n 的值大约为逻辑处理器数昌的 5/8,大笃数情况下是这样。除了较大的 SPARC 系统,其中n的值约为逻辑处理器的 5/16。 | |   | -XX:ConcGCThreads=n  | 设置垃圾收集器井发阶段使用的线程数莓,设置 n 大约为 ParallelGCThreads 的1/4。 | |   | -XX:InitiatingHeapOccupancyPercent=45 | 老年代大小达到该阈值,就融发 Mixed GC,默认值为45。 | |   | -XX:G1HeapWastePercent=5  | 设置浪费的堆内存百分比,当可回收百分比小于浪费百分比时,JVM 就不会启动混合垃圾收,从而避免昂贵的 GC 开销。此参数相当于用来设置允许垃圾对象占用内存的最大百分比。 | |   | -XX:G1 MixedGCCountTarget=8  | 设置在标记周期完成之后,最多执行多少次 Mixed GC,默认值为 8。 | |   | XX:G1OldCSetRegionThresholdPercent=10  | 设置在一次 Mixed GC 中被收集的老年代的比例上限,默认值是 Java 堆的10%,这是个实验参数。需用-XX:+UnlockExperimentalvMOptions 解锁试验参数后,才能使用该参数。 | |   | -XX;G1ReservePercent=10  | 设置预留空闲内存百分比,虚拟机会保证 Java 堆有这么多空间可用,从而防止对象晋升时无空间可用而矢败,默认值为 Java 堆的 10%。 | |   | -XX:-G1PrlntHeapRegions  | 输出Region被分配和回收的信息,默认 false。 | |   | -Xx:-G1PrintRegionLivenesslnfo  | 在清理阶段的并发标记环节、输出堆中的所有 Regions 的活跃度信息,默认false。 | | Shenandoah  | -XX:+UseShenandoahGC  | 使用 UseshenandoahGC,这是个实验参数,需用-XX:+UnlockExperimentalVMOptions 解锁试验参数后,才能使用该参数;另外该参数只能在 Open JDK 中使用,Oracle JDK 无法使用。  | | ZGC | -XX:+UseZGC  | 使用ZGC,这是个实验参数,需用-XX: +UnlockExperimentalvMOptions 解锁试验参数后,才能使用该参数。 | | Epsilon | -XX:+UseEpsilonGc  | 使用EpsilonGC,这是个实验参数需用-XX:+UnlockExperimentalvMOptions解锁试验参数后,才能使用该参数。

可以看到与垃圾收集器相关的 JVM 参数有这么多。这个表应该是比较全的垃圾回收器相关 JVM 参数了,这些参数非常的繁多,并且不同的垃圾收集器参数也是不一样的,使用起来也比较的复杂。

特别是 CMS 以及 G1 给我们留了好多的配置项去控制它们的行为。

那么这些参数怎么样配置,配置阈值多大讲究也是非常多的。因此,实际项目中怎么样调优这些参数也是一大难点。不过你不用担心,在后面的实战章节中,我会手把手带你探讨怎么样调优这些参数。

那么就本课时来说,你只要掌握两点,就算达到学习的目标:

首先,希望你知道收集器相关的 JVM 参数,做好笔记,并留个印象下来,将来用到的时候记得回来查。

第二,希望你能够通读一下表里面的参数,并且要能够理解这些参数大致上的作用。因为这些参数也能够帮助你检验前面是不是真正理解了各款垃圾收集器执行的过程。比如,当你看到这样的一个参数:-XX:CMSInitiatingoccupancyFraction=68 的时候,要能够知道这个参数的作用是说当老年代的占用率达到多少,就触发垃圾收集。另外你还应该知道,为什么不能到内存满了才去执行垃圾收集,而应该预留一些内存。这主要是因为内存碎片的问题。如果你看到这个参数没有办法理解背后的思想。那么就应该复习一下前面 CMS 垃圾收集相关的知识了。

再比如,但你看到这个参数:-xx:G1HeapWastePercent=5 的时候,要知道为什么 G1 会存在空间浪费,这是因为 G1 在回收垃圾的时候,会把 Region 里面活跃的对象复制到空闲的 Region 里面去,再去清空掉这个 Region,用的是复制算法,所以会存在空间浪费。

当然了,这里只是举两个例子,其他的参数你也应该去通读一下。

好,现在我们距离见面目调优实战已经越来越近了,你再坚持一下,就可以进入到项目实战的内容了。

另外这里需要吐槽一下整理这样的一个表格,虽然只有几十个参数,但是我花费了的业余时间才整理出来,我在整理的过程中发现市面上的博客或者是文章质量普遍不高。比如有些参数已经废弃好久了,根本就用不了了,而文章却没有标记出来这些参数。其实挺容易给读者造成困扰的。我的做法是把这些参数一个一个去亲测。如果发现不能用的话,再把这个参数给删掉。那么在这里建议你以后在写笔记的时候,一定要注意自己所使用的 JDK 版本。

上一篇:9.27美团面试


下一篇:Java全栈工程师