JDK 20 G1/并行/串行 GC 更改


又一个正在进行中的 JDK 版本——JDK 20 GA即将发布

并行GC

  • Parallel GC 唯一值得注意的改进是在 Full GC 期间并行处理跨越压缩区域JDK-8292296的对象。
    工作线程不是在最后有一个单线程阶段来迭代和修复这些对象,而是收集穿过其本地压缩区域的对象并自行处理它们。贡献者 M. Gasson报告说,在某些情况下,完整的 gc 暂停时间减少了 20%。

串行GC

  • Serial GC 没有显着变化。清理串行 GC 代码有很多变化。

G1 GC

  • JDK-8210708通过删除跨越整个 Java 堆的标记位图之一,将 G1 本机内存占用减少了 Java 堆大小的约 1.5%。G1 博客文章中的并发标记包含对更改的详尽讨论。
    该帖子还废弃了原始G1 论文中有关并发标记的信息。论文中没有太多关于当前 G1 的准确信息。
  • 实际上,在某种程度上,该博文也已经过时了:JDK-8295118移动了一个有时冗长的准备部分,用于Clear Claimed Marks从并发启动垃圾收集暂停中调用的并发标记。一个名为的新阶段Concurrent Clear Claimed Marks将显示在带有gc+marking=debug日志记录的日志中。
  • 为G1 中未来的区域固定支持做准备, [url=https://bugs.openjdk.org/browse/JDK-8256265]JDK-8256265[/url]在处理由用户固定的区域(或由于 Java 堆中没有剩余空间而无法撤离)时降低了并行化粒度。任务粒度现在是区域的一部分,而不是在每个线程的基础上分发整个区域。
    这使得线程更好地共享工作,显着减少了非生产性的等待完成。
  • JDK-8137022使细化线程控制更具适应性。以前,在垃圾收集暂停期间,G1 会计算离散阈值,在该阈值处,特定的细化线程在增变器时间被激活(和停用)以帮助细化。此计算基于用户希望-XX:G1RSetUpdatingPauseTimePercent在垃圾收集暂停期间花在精炼卡片上的允许时间(选项)、下一次暂停的最近间隔和许多魔法。
    由于不直接观察应用程序,例如考虑最近传入和细化线程处理工作的速率,以及距离下一次垃圾收集的预期剩余时间,细化线程控制强烈倾向于唤醒并在突发中做比必要更多的工作时尚,以避免为系列暂停留下太多工作。这种行为不仅在优化线程中浪费了 CPU 周期,而且还有另一个缺点:Java 堆上几乎没有未优化的卡片。这听起来不错,但在一定水平以下,这可能会适得其反。每当有一张新的、未访问过的卡时,写屏障需要执行更多的工作,如此处所述,不是卡保留在队列中以供稍后处理的情况。
    精炼线程的额外活动不仅会占用 cpu 资源,而且 G1 还会反复精炼相同的卡片,而不会减少暂停时留下的工作量。
    考虑到变更活动,G1 在暂停之间更好地分配细化线程活动,并将更多卡片留在细化任务队列中更长时间,这降低了新卡片的生成率并更确定地实现了用户的意图(即)-XX:G1RSetUpdatingPauseTimePercent。最后,这通常会减少应用程序的 cpu 周期,从而提供更好的吞吐量。
    与旧的细化控制相关的几个 G1 垃圾收集器选项已被废弃,导致 VM 在使用时在启动时退出。发行说明详细介绍了它们。
  • G1 使用线程本地分配缓冲区 (PLAB) 来减少垃圾收集暂停期间的同步开销。PLAB 根据最近的分配模式调整大小,以减少垃圾收集暂停结束时这些缓冲区中未使用的空间。如果几乎不需要分配,它们就会缩小,否则就会增长。这种每次收集暂停适配适用于在垃圾收集之间具有相当恒定分配的应用程序;然而,如果分配非常突发,这就不会很好地工作。在垃圾收集期间几乎没有分配后,PLAB 将太大一些垃圾收集,浪费空间,或者在分配峰值时太小,浪费 cpu 周期重新加载 PLAB。
    因此,在某些平台上,我们注意到秒级内出现非常长的暂停峰值。JDK-8288966中的更改在垃圾收集期间增加了一些合理积极的 PLAB 大小调整,以应对这些情况。
  • 在 JDK 20 中花费了大量精力来改进用于确定年轻一代大小的预测,年轻一代最终负责垃圾收集需要多长时间(错误跟踪器查询提供了概述)。
    -XX:MaxGCPauseMillis更好的预测使 G1 更好地观察暂停时间目标(由 这会在允许的目标范围内增加暂停时间,但会显着减少垃圾收集的次数。我们测量了通过这些更改在垃圾收集暂停上花费的时间减少了 10-15% 的应用程序。
  • JDK 20 默认通过JDK-8293861禁用预防性垃圾回收。JDK 19 中引入了预防性垃圾回收,以避免 G1 在垃圾回收期间没有足够的 Java 堆内存来疏散对象(也称为“疏散失败”)。经历疏散失败的区域的处理传统上相当缓慢,因此该功能的论点是最好先进行没有此类疏散失败的垃圾收集,以期释放足够的内存以完全避免这些疏散失败。
    问题是如何正确地预测这种情况。G1 用于确定是否开始预防性收集的预测被证明是次优的,并且经常不必要地过早地开始预防性收集。这浪费时间。还有很多情况下不会触发预防性收集,导致应用程序无论如何都会遇到疏散失败。最后,这些类型的垃圾收集使垃圾收集更加不规则,如果发生这种情况,通常会使预测变得更加困难。
  • JDK-8297247引入了一个新的GarbageCollectorMXBean调用,它报告 G1 Remark 和 Cleanup 暂停的发生和持续时间。这些暂停现在也会更新内存池信息。G1 Concurrent GCG1 Old Gen MemoryManagerMXBean

总而言之,我相信值得升级的垃圾收集有重要的补充,即使只是在 JDK 21 之后。