Java 24 将减少对象头大小以节省内存

JEP 450:紧凑对象头

JEP 450(紧凑对象头)已被纳入JDK 24的开发计划,并已合并到主干代码中。该特性目前处于实验阶段,旨在通过缩小HotSpot虚拟机中对象头的固定大小,优化堆内存的使用,从而减少整体堆大小、提高部署密度并增强数据局部性。

当前实现的概述

HotSpot将所有对象存储在Java堆中,这是进程“C堆”中的连续区域。Java对象总是通过引用处理,因此:

  1. 引用对象的局部变量包含从Java方法栈帧到Java堆的指针。
  2. 引用类型的对象字段从一个Java堆位置指向另一个位置。

Java引用的目标地址始终是对象头的起始位置(在当前版本的HotSpot中,对象头是固定的)。每个对象都有一个对象头,数组对象还额外有32位用于存储数组长度。对象头的第一个64位是标记字(mark word),用于存储实例特定的元数据,例如:

  • 垃圾回收:存储对象的年龄(可能包括转发指针)。
  • 哈希码:存储对象的稳定哈希码。
  • :存储对象的锁/监视器。

在某些情况下,标记字会被覆盖并替换为指向更复杂数据结构的指针,这稍微增加了紧凑对象头的实现复杂度。标记字之后是类字(klass word),用于计算指向该类类型共享元数据的指针,支持方法调用、反射、类型检查等操作。

类元数据存储在元空间(metaspace)中,位于Java堆之外,但仍在JVM进程的C堆内。由于它们不在Java堆中,类元数据不需要Java对象头,与反射中使用的类对象也不同。

在64位架构上,类字原本是一个完整的机器字(64位),这很浪费内存,因此引入了“压缩类指针”技术,将类指针编码为32位。因此,在64位HotSpot中,非数组对象的“头税”为96位。相比之下,Python的头税曾高达308字节,而JEP 450的目标是将对象头的总大小进一步减少到64位。

紧凑对象头的引入

作为OpenJDK“Project Lilliput”的一部分,新实现在两个64位平台(x64和AArch64)上减少了对象头的大小。其主要目标包括:

  1. 在目标平台上将吞吐量和延迟开销控制在5%以内,仅在少数情况下接近该限制。
  2. 在非目标平台上不引入可测量的吞吐量或延迟开销。

目前的测试显示,只有极少数情况下出现性能回退(部分问题已在JDK 24中修复)。亚马逊的测试表明,许多工作负载在吞吐量上有所提升,某些工作负载的CPU利用率甚至降低了30%。

该项目利用了Java工作负载中对象平均大小为32到64字节的观察结果,这意味着头税约占20%。因此,即使对象头大小的微小改进也可能显著减少堆占用,进而改善数据局部性并降低垃圾回收压力,带来进一步的性能提升。

为实现头部的缩减,标记字和类字被合并为一个64位字,具体布局如下:

  1. 用于标识对象类类型的位数从32位减少到22位,意味着JVM进程可加载的类类型数量约为400万。
  2. 哈希码的大小保持不变。
  3. 锁定操作不再覆盖标记字,从而保留压缩类指针。
  4. 垃圾回收转发操作变得更加复杂,以保留对压缩类指针的直接访问。
  5. 保留了4位未使用的位,用于未来增强(如Project Valhalla)。

如果Java锁发生争用,新实现需要查找存储锁信息的辅助数据结构的地址。这种称为“对象监视器表”的机制在JDK 22中实现,并通过默认启用的新开关UseObjectMonitorTable激活。紧凑对象头依赖于该机制。

如果没有发现重大问题,该特性将作为实验性功能随JDK 24于2025年3月发布。长期目标是使其成为支持平台上的唯一头部表示形式,但这可能需要多个版本的迭代,并依赖于对实际工作负载的广泛测试和性能无回退。此外,还在探索是否可能将头部大小进一步减少到32位。

一旦JDK 24(测试版或正式版)发布,应用团队可以通过命令行开关-XX:UseCompactObjectHeaders激活该特性,并测试其工作负载的性能变化。

阅读 116
0 条评论