通过自定义分配器解决 ZGC 中的碎片问题
2024 年 6 月 19 日这里介绍的工作是作为 Oracle、乌普萨拉大学和 KTH 之间的联合研究项目的一部分进行的。请关注 inside.java 上的博客系列,以了解有关 Oracle 斯德哥尔摩开发办公室进行的 JVM 研究的更多信息。
通过自定义分配器解决 ZGC 中的碎片问题:摘要
我叫 Joel,目前正在乌普萨拉大学攻读计算机与信息工程专业,专注于软件工程,即将完成我的 5 年制学位。这篇文章详细介绍了我 2024 年春季在 Oracle 斯德哥尔摩办公室的 GC 团队进行的硕士论文工作。我与 Casper Norrbin 和 Niclas Gärds 紧密合作,他们也于今年春季在 Oracle 进行了与我相同的领域的硕士论文工作。
问题陈述
ZGC 和其他垃圾收集器通常使用 bump-pointer 分配,这对于顺序分配来说很有效,但随着时间的推移会导致碎片。碎片是指创建无法轻松重用的内存间隙,这需要对活动对象进行代价高昂的重新分配。本研究的目标是通过在 bump-pointer 分配器旁边使用基于空闲列表的分配器来减少 ZGC 中重新分配的需要,该分配器可以在某些情况下更有效地跟踪和利用碎片化的内存。
方法
我的研究重点是调整分配器,使其更适合在 ZGC 中使用,该分配器基于 Masmano 等人 的两级隔离拟合 (TLSF) 分配器。我贡献的主要调整是
- 0 字节头:通过利用 ZGC 中的信息,分配器引入了 0 字节头,这显著减少了内部碎片。下图显示了 1) 参考设计,2) 通用设计,以及 3) 优化的 0 字节头。
-
ZGC 小页面:将分配器限制在 ZGC 的有限大小 (2MB) 和分配大小范围 ([16 B, 256 KB]) 内使用,内部表示可以更有效地存储和使用。下图显示了如何将大量的第一级和第二级扁平化为一个 64 位字。
-
并发:使用无锁机制支持对分配器的并发操作,该机制考虑了许多不同的问题和用例。
0 字节头尤其值得注意,因为它是一系列对分配器的较小调整实现的。诸如延迟合并、将支持的堆大小减少到 ZGC 小页面的大小,以及利用已经是 Java 对象头一部分的信息等调整,使得 0 字节头成为可能。此外,并发可以通过多种方式解决,但我研究中的无锁解决方案由于上述调整而变得更容易实现。如果没有这些调整,实现无锁解决方案将要复杂得多。
结果
经过调整的分配器显示出在 ZGC 中使用的巨大潜力,重点是分配内存。
-
性能:对于单次分配,新分配器与参考实现的性能相当。但是,对于单次释放和现实世界的分配模式,它略慢。考虑到碎片的显著减少,这种权衡是可以接受的。
-
内存效率:引入 0 字节头和其他优化导致内部碎片显着减少。内存效率的提高表明新分配器在管理碎片化内存方面是有效的。
结论
我的工作表明,为 ZGC 等垃圾收集器定制分配器是解决内存碎片的一种可行方法。经过调整的分配器不仅减少了对代价高昂的重新分配的需求,而且还提高了整体内存效率。我已经证明,对 TLSF 进行调整以在 ZGC 中使用具有很大的潜力,这可能也适用于其他分配器。我工作的最明显下一步是将分配器集成到 ZGC 中(Niclas Gärds 在与我同时进行的论文中已经做到了这一点)。其他需要考虑的领域包括来自 Lilliput 项目的 Java 中新的最小分配大小,以及解决经过调整的分配器的并发实现中的饥饿问题。
最后,我要感谢斯德哥尔摩 Oracle 办公室的所有人,感谢他们分享他们的知识,让我感觉自己是团队中的一员,并营造了一个鼓励学习的环境。感谢 Erik Österlund 和 Tobias Wrigstad 在整个项目中给予我稳定的支持、知识和指导。最后,感谢 Casper 和 Niclas 让这个春天变得特别而令人兴奋!
如果您想了解更多关于我的工作的信息,请查看我在 DiVA 上发布的报告,该报告提供了本文中解释的概念的更多细节和深度,以及此处未涵盖的其他领域。