JDK JDK 学习手册8 → 27 · JDK 专用学习站
DEEPDIVE · RUNTIME · JEP 450 / 519

紧凑对象头:从 96/128 位到 64 位

JDK 25 · 深钻主题 03 / 03 · 预计 45 分钟
深入四步深钻JEP 450JEP 519
一句话紧凑对象头把 64 位 HotSpot 的对象头从 96/128 位压到 64 位。JEP 519 在 JDK 25 把它从实验特性转为产品特性,但明确不改变默认布局:默认仍是旧布局,需要显式 -XX:+UseCompactObjectHeaders。
本质:省的是每个对象的固定开销,不是字段本身;对象越小、数量越多,收益越明显,但必须用自己的堆与延迟数据验证。

学完你要能自己回答

  1. 对象头承担哪些职责?为什么旧布局是 96 或 128 位?
  2. 64 位紧凑头如何同时放下类指针、identity hash、锁状态和 GC 年龄?
  3. JEP 519 转正后,JDK 25 默认开启了吗?启用条件是什么?
  4. 紧凑对象头对堆占用、GC、启动和工具链分别有什么影响?
  5. 怎样评估一个服务是否应该开启紧凑对象头?
01 对象头是四个子系统的交界02 省下的 4–8 字节,为什么值得做

对象头是四个子系统的交界01

堆里的每个对象都有一段固定头部。HotSpot 用这段头部同时服务四件事:GC 的标记/转发与年龄、类型系统的方法调用与类型检查、锁状态、以及一旦计算就稳定的 identity hash。头部大小与对象类型、数组长度、字段内容无关。

  • GC:转发指针、对象年龄、标记状态。
  • 类型系统:指向 Klass 的类指针,方法调用、反射、类型检查都要用。
  • 锁:轻量锁/重量锁的状态与监视器信息。
  • identity hash:System.identityHashCode 第一次计算后要保持稳定。
64 位 HotSpot 布局组成头部大小
压缩类指针(常见)mark word 64 位 + compressed class 32 位96 位 / 12 字节
未压缩类指针mark word 64 位 + class 64 位128 位 / 16 字节
紧凑对象头单一 64 位头,类指针压缩进头内64 位 / 8 字节
32 位平台不在 JEP 450 目标内本来就更小;25 已移除 32 位 x86

旧布局把头部拆成 mark word 和 class word。mark word 会被锁、hash、GC 复用/覆盖;class word 相对稳定,保证类型信息始终可读。紧凑布局则把两者合并,把压缩类指针放进同一个 64 位字里。

数组长度不属于对象头,JEP 450 明确不改变数组元数据与元素编码。讨论「对象头 8 字节」时,不要把它和数组长度混在一起。

省下的 4–8 字节,为什么值得做02

Java 对象普遍偏小。Project Lilliput 的实验显示,很多工作负载的平均对象大小只有 32–64 字节,头部可能占活数据的 20% 以上。每个对象省 4–8 字节,乘以对象数量,就是可观的堆占用与缓存局部性收益。

维度预期变化说明
堆占用下降对象越小人越多,比例收益越大
GC 压力通常下降活数据变少,标记/复制工作量降低
CPU / 吞吐基准中持平或改善JEP 519 引用 SPECjbb2015 等结果
延迟不应显著变差JEP 450 目标是不引入超过 5% 的偶发开销
工具兼容需要验证依赖头部布局的 agent / 分析工具要回归
默认行为不变JEP 519 不把紧凑头设为默认

JEP 519 引用的数据包括:SPECjbb2015 堆空间少 22%、CPU 时间少 8%;GC 次数少 15%;高并行 JSON parser 快 10%。这些是特定基准的结果,不是你的服务承诺。JEP 450 早期采用者报告的活数据下降约 10%–20%,同样取决于对象分布。

紧凑对象头不是「免费加速」。它改变的是对象布局,收益与对象图、GC、分配速率强相关;必须用相同堆上限、相同负载做 A/B。
01 64 位头怎么塞下类指针、hash、锁与 GC02 启用路径:产品特性,但默认关闭

64 位头怎么塞下类指针、hash、锁与 GC01

紧凑布局取消了 mark word 与 class word 的分工,把压缩类指针直接并入 64 位头。类指针从 32 位进一步压缩到 22 位;identity hash 保持 31 位;另外预留 4 位给 Project Valhalla,剩余位用于 GC 年龄、锁标记与转发标记。

  • 类指针:22 位压缩类指针,要求 UseCompressedClassPointers 开启。
  • identity hash:31 位,大小与旧布局相同;一旦计算仍保持稳定。
  • 锁:轻量锁/监视器锁通过 tag 位表示,不再用带标签指针覆盖整个 mark word,因此类指针始终保留。
  • GC:转发时需要保留类指针,引入 self-forwarded tag 等机制;具体编码属于实现细节。
  • Valhalla:预留 4 位,为未来值类型留空间。

这意味着锁、hash、GC 不能再像旧布局那样随意覆盖整个 mark word。实现必须在 64 位内协调这些状态,代价是更复杂的转发与锁路径;收益是每个对象固定少 4–8 字节,并且类指针与对象数据更接近,缓存局部性更好。

位分配是 HotSpot 实现细节,不是 Java API。不要用 Unsafe、JNI 或 agent 去假设某一位的含义;这些代码在布局变化时会失效。

启用路径:产品特性,但默认关闭02

JEP 450 在 JDK 24 以实验特性引入,需要 -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders。JEP 519 在 JDK 25 把它转为产品特性:不再需要解锁实验选项,但默认值仍是 false。

java -XX:+PrintFlagsFinal -version | grep UseCompactObjectHeaders
bool UseCompactObjectHeaders = false {product lp64_product} {default}
java -XX:+UseCompactObjectHeaders -XX:+PrintFlagsFinal -version | grep UseCompactObjectHeaders
bool UseCompactObjectHeaders = true {product lp64_product} {command line}

它依赖压缩类指针。若同时关闭 UseCompressedClassPointers,JVM 会警告并自动禁用紧凑对象头——不是启动失败,而是静默回退,所以必须核对最终 flag 值。

java -XX:+UseCompactObjectHeaders -XX:-UseCompressedClassPointers \
     -XX:+PrintFlagsFinal -version 2>&1 | grep -E "warning|UseCompactObjectHeaders"
OpenJDK 64-Bit Server VM warning: Option UseCompressedClassPointers was deprecated in version 25.0 and will likely be removed in a future release.
OpenJDK 64-Bit Server VM warning: Compact object headers require compressed class pointers. Disabling compact object headers.
     bool UseCompactObjectHeaders = false {product lp64_product} {command line}
JDK 25 中 UseCompressedClassPointers 本身也已弃用并会警告。升级时不要一边关压缩类指针、一边以为紧凑头生效;用 PrintFlagsFinal 看最终值。
01 评估步骤:先基线,再 A/B02 常见坑

评估步骤:先基线,再 A/B01

紧凑对象头的收益高度依赖对象图。正确做法不是「看到转正就开」,而是用同一份构建、同样的堆上限和负载,对比默认布局与紧凑布局。

  1. 记录基线:堆占用、GC 次数/停顿、分配速率、P99 延迟、CPU。
  2. 只加 -XX:+UseCompactObjectHeaders,保持堆上限、GC、线程数不变。
  3. 用 PrintFlagsFinal 确认最终值为 true,排除被其他参数静默禁用。
  4. 跑真实流量或可复现的压测,至少覆盖启动、稳态、峰值和一次 Full GC。
  5. 回归 native agent、JFR/堆转储分析、锁与对象布局相关工具。
  6. 灰度发布,保留关闭开关;如果延迟或 CPU 变差,能快速回退。
java -XX:+PrintFlagsFinal -version | grep UseCompactObjectHeaders
java -XX:+UseCompactObjectHeaders -Xlog:gc*:file=gc.log -jar app.jar

评估时不要只看「堆小了」。堆占用下降可能伴随对象搬移、TLAB 大小、GC 区域利用率的变化;延迟和 CPU 才是是否上线的决定因素。

JEP 519 的基准数字可以作为预期方向,但不能替代你自己的压测。对象越小、数量越多,越值得试;大对象为主的服务收益可能很小。

常见坑02

  • 把转正当默认:25 默认仍是 96/128 位,必须显式开启。
  • 关闭压缩类指针:紧凑头会被静默禁用,只留下 warning。
  • 在 JDK 24 忘记解锁实验选项:24 需要 UnlockExperimentalVMOptions,25 不需要;脚本要按版本区分。
  • 用 Unsafe 假设头部偏移:布局是运行时选择,不是 API 契约。
  • 只看堆占用:还要看 GC 停顿、CPU、P99 和启动时间。
  • 忽略工具链:native agent、堆分析器、锁监控工具都要回归。
  • 以为能解决所有内存问题:字段、数组内容、对象图引用才是大头;头部只是固定开销。
如果服务依赖 32 位 x86 JVM,先解决 JEP 503 的移除问题;紧凑对象头只针对 64 位平台,救不了 32 位 x86 部署。
01 合上页面,先回答02 版本与边界一页表

合上页面,先回答01

先猜再看紧凑对象头是 4 字节吗?
不是。它是 64 位 / 8 字节;旧布局在 64 位上通常是 96 位(12 字节)或 128 位(16 字节)。省下的是 4–8 字节。
先猜再看JDK 25 默认开启紧凑对象头吗?
不开启。JEP 519 只把它从实验特性转为产品特性,明确不改变默认布局;需要 -XX:+UseCompactObjectHeaders。
01. 对象头承担哪四类职责?
GC 的转发/年龄/标记,类型系统的类指针,锁状态,以及稳定的 identity hash。紧凑布局必须在 64 位内协调这四类信息。
02. 为什么紧凑头依赖压缩类指针?
它把类指针压缩到 22 位并塞进 64 位头;关闭 UseCompressedClassPointers 后没有这套编码,JVM 会警告并禁用紧凑头。
03. 紧凑头会改变 class 文件或 Java API 吗?
不会。它是 JVM 运行时的对象布局选择,class 文件格式和 Java API 不变;但依赖头部布局的 native/agent 工具需要验证。
04. 什么样的负载最可能受益?
对象小、对象数量多、活数据中头部占比高的负载。大对象为主或 CPU 已经受限于计算的负载,收益可能很小,必须压测。

版本与边界一页表02

事项JDK 24JDK 25
支持级别实验特性产品特性(JEP 519)
开启方式UnlockExperimentalVMOptions + UseCompactObjectHeaders只需 UseCompactObjectHeaders
默认值falsefalse(JEP 519 非目标:不改默认)
头部大小64 位(开启时)64 位(开启时)
压缩类指针必需必需;关闭则紧凑头被禁用
32 位 x86仍支持已移除(JEP 503)

一句话总结:紧凑对象头是 25 的「可用产品特性」,不是「默认行为」。它把固定开销从 12/16 字节压到 8 字节,收益真实但依赖对象分布;上线前用 flag 核对、压测和灰度,而不是相信基准数字。

“转正”只描述支持级别。JEP 519 的非目标写得很清楚:不把紧凑对象头设为默认布局。把这句话记住,能避开大多数版本判断题。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。