JEP 345:G1 的 NUMA 感知内存分配
概述
通过实现 NUMA 感知的内存分配来提升大型机器上的 G1 性能。
非目标
- 不以实现除 G1 以外的 NUMA 支持收集器为目标。
- 不以支持除 Linux 以外的操作系统为目标。
- 不以使 G1 的其他部分具备 NUMA 感知能力为目标,例如任务队列窃取、记忆集或细化。
动机
现代多插槽机器越来越多地具有非统一内存访问(NUMA)特性,也就是说,内存与每个插槽或核心的距离并不相等。插槽之间的内存访问具有不同的性能特征,通常访问距离更远的插槽时会有更高的延迟。
通过 -XX:+UseParallelGC
启用的并行收集器多年来一直是支持 NUMA 的。这有助于提高在多个插槽上运行单个 JVM 的配置性能。其他 HotSpot 收集器则没有这一特性的加持,这意味着它们无法利用这种垂直多插槽 NUMA 扩展的优势。特别是大型企业应用程序,通常倾向于在多个插槽上使用大堆配置运行,但又希望获得在单个 JVM 中运行所带来的管理便利性。使用 G1 收集器的用户正越来越多地遇到这一扩展瓶颈。
描述
G1 的堆被组织为一组固定大小的区域(region)。一个区域通常是一组物理页,不过当使用大页(通过 -XX:+UseLargePages
)时,多个区域可能组成一个物理页。
如果指定了 +XX:+UseNUMA
选项,那么在 JVM 初始化时,区域将均匀分布在所有可用的 NUMA 节点上。
在一开始固定每个区域的 NUMA 节点有些不够灵活,但可以通过以下增强功能来缓解这一问题。为了给一个 mutator 线程分配一个新对象,G1 可能需要分配一个新区域。它会优先从当前线程绑定的 NUMA 节点中选择一个空闲区域,以便该对象将保留在年轻代的同一 NUMA 节点上。如果在为 mutator 分配区域时,同一 NUMA 节点上没有空闲区域,那么 G1 将触发垃圾回收。另一个待评估的想法是按距离顺序搜索其他 NUMA 节点以寻找空闲区域,从最近的 NUMA 节点开始。
我们不会尝试将老年代中的对象保持在同一个 NUMA 节点上。
巨大的区域在此分配策略中被排除在外。我们不会对这些区域采取任何特殊措施。
测试
现有的使用 -XX:+UseNUMA
选项的测试应该能够发现任何正确性问题。我们假设使用 NUMA 硬件进行测试。
当 NUMA 感知分配被关闭时,与原始代码应该没有性能差异。
风险与假设
我们假设大多数短生命周期的对象通常由分配它们的线程访问。这在大多数面向对象程序中的短生命周期对象中肯定是正确的。然而,也有一些程序并不完全符合这个假设,因此在某些情况下可能会出现性能下降。此外,其优势还取决于底层系统的 NUMA 特性程度以及在线程频繁迁移的高负载情况下,线程在 NUMA 节点之间迁移的频率。