BLOG
技术拆解 010|Java 27:三个新特性贴源码拆解——64 位对象头、原始类型模式匹配、结构化并发
2026 年 9 月 15 日,JDK 27 正式发布(GA,build 35),带来 9 个 JEP。多数报道停在新闻稿层面:一个编号、一句摘要、一段示例代码。这篇文章换一个做法——直接贴源码。这一版有三个特性各站一个层面:JEP 534 紧凑对象头(HotSpot 运行时)把 64 位架构上的对象头从 96 bits 压到 64 bits;JEP 532 原始类型模式匹配(javac)让 instanceof 和 switch 支持全部原始类型;JEP 533 结构化并发(java.base API)把一组相关线程任务收进可关闭的作用域。本文拆六件事:是什么、核心机制一路拆到源码行号、技术评估、值不值得跟进、怎么开、以及从读源码到给 OpenJDK 提补丁的路。
一、这是什么
先把全貌摆清楚。JDK 27 共 9 个 JEP,按官方项目页核对:JEP 523 G1 全环境默认、JEP 527 TLS 1.3 后量子混合密钥交换、JEP 531 Lazy Constants(第 3 次预览)、JEP 532 原始类型模式匹配(第 5 次预览,本文聚焦)、JEP 533 结构化并发(第 7 次预览,本文聚焦)、JEP 534 紧凑对象头默认启用(正式特性,本文聚焦)、JEP 536 JFR 进程内数据脱敏、JEP 537 Vector API(第 12 期孵化)、JEP 538 PEM 编码(第 3 次预览)。注意序号不连续,没有 524–530、535。
三个聚焦特性各管一个层面,成熟度差别很大。JEP 534 是正式特性,JDK 27 默认启用,改的是 HotSpot 的对象内存布局,对 Java 代码完全透明。沿革:JEP 450(JDK 24)实验引入,JEP 519(JDK 25)转正不默认,JEP 534(JDK 27)默认启用,Owner 是 Roman Kennke。JEP 532 是语言层预览特性,需 --enable-preview,解决积攒二十多年的别扭事:模式匹配、instanceof、switch 长期只认引用类型。JEP 533 是 API 层预览特性,同样要 --enable-preview,把「一组相关任务」从线程池的自由状态收编成有明确边界的工作单元,Authors 是 Alan Bateman、Viktor Klang、Ron Pressler,从 JEP 428(JDK 19 孵化)一路做到现在。
一条贯穿线把三者串起来:省内存、编译器减负、并发好管,全是工程主义——没有新范式,全在还旧账。
二、核心机制
引用规则先说明:「JEP 口径」来自 openjdk.org 官方 JEP 页面;「源码所见」是 openjdk/jdk 仓库 tag jdk-27+35 下的源码原文,带文件路径和行号。
2.1 JEP 534:96 bits 压到 64 bits,markWord.hpp 的位图是全部答案
64 位架构上,旧对象头是 mark word(64 bit)加 class word(compressed class pointers 开启时 32 bit),合计 96 bits。JEP 450 的思路是取消两段分界,把压缩后的类指针塞进 mark word。官方布局图(JEP 450 原文):
Header (compact):
64 42 11 7 3 0
[CCCCCCCCCCCCCCCCCCCCCCHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHVVVVAAAASTT]
(Compressed Class Pointer) (Hash Code) /(GC Age)^(Tag)
(Valhalla-reserved bits)(Self Forwarded Tag)
类指针进一步压到 22 bits,hash code 大小不变,给 Project Valhalla 预留 4 bits。这是 JEP 口径,下面源码逐位对上。markWord.hpp L43–49 的头注释位布局图:
// 64 bits (without compact headers):
// unused:22 hash:31 valhalla:4 age:4 self-fwd:1 lock:2
// 64 bits (with compact headers):
// klass:22 hash:31 valhalla:4 age:4 self-fwd:1 lock:2
一句话说清:原来最高 22 位是 unused,现在塞进 klass;其余五个位域纹丝不动,加总正好 64 位。常量定义(同文件 L116–154,节选关键行):
static const int lock_bits = 2;
static const int self_fwd_bits = 1;
static const int age_bits = 4;
static const int hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits;
// Used only with compact headers: the (narrow) Klass* lives in bits 43 to 64.
static constexpr int klass_bits = 22;
三个细节:hash 显式 cap 在 31 位,与 JEP 450「hash code 大小不变」一致;22 位 narrow Klass 存在 bit 43–64;位域从低位到高位依次 lock(2)→self-fwd(1)→age(4)→valhalla 预留(4)→hash(31)→klass(22),注释、常量、加总三处自洽。开关层面(globals.hpp L131–132):
product(bool, UseCompactObjectHeaders, true,
"Use compact 64-bit object headers in 64-bit VM")
LP64 平台的正式产品开关,默认 true。对照历史:JEP 450 时代它是 experimental 开关,需 -XX:+UnlockExperimentalVMOptions;JDK 27 它就是一行普通 product flag。同文件 L150 写明 32 位 VM 恒为 false。想关回旧布局用 -XX:-UseCompactObjectHeaders,旧 96 位布局本版仍保留,JEP 534 明确把移除旧布局列为非目标。口径边界:锁操作不再覆盖 mark word、GC 转发新增 self-forwarded tag 等机制描述来自 JEP 450 文档,self_fwd_bits 在注释层面得到印证,ObjectMonitor 与 GC 转发的具体 C++ 代码不在本文范围,不展开。
2.2 JEP 532:原始类型模式匹配,javac 在降译阶段做了什么
三个历史限制(JEP 口径):switch 的模式匹配不支持原始类型模式;record 模式的原始类型组件必须严格同类型(JsonNumber(double a) 不能写 JsonNumber(int age)),而语言其余地方有自动 widening;instanceof 只支持引用类型。JEP 532 一次全开,switch 选择子扩到 long/float/double/boolean。语义核心是 exactness——转换无信息丢失即 exact,long→int、int→float 是否 exact 取决于运行时输入值,需要运行时测试;unconditionally exact 则编译期即可断定永不丢失(type-based 与 value-based 两类),dominance、exhaustiveness 规则随之扩展,浮点 case 常量按表示等价判重。官方示例(JEP 532 原文):
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i; // 原 default 分支
}
int i = 1000;
if (i instanceof byte b) { ... } // false,不进入分支
float f = 1000.0f;
f instanceof int; // true (exact)
源码所见(TransPatterns.java)。instanceof 原始类型测试在降译阶段被改写,L200–221:
// $expr instanceof $primitiveType
// =>
// $expr instanceof T $temp && $temp instanceof $primitiveType
if (tree.erasedExprOriginalType!=null && ...) {
BindingSymbol temp = new BindingSymbol(Flags.FINAL | Flags.SYNTHETIC, ...);
// 先对擦除前类型做绑定模式匹配,再对临时变量做原始类型测试
result = translate(resultExpr); // 两段式 && 复合表达式
}
一句 expr instanceof int 并不直接生成原始类型测试字节码,而是两段与运算:先把值收进合成的 FINAL | SYNTHETIC 临时变量(名字带 syntheticNameChar,不与用户变量撞车),再问「这次转换丢没丢信息」。第一段负责把值安全带回来,第二段是运行时 exactness 判定。L772–828 的 makePrimitive 给出另一半答案:原始类型经 ConstantBootstraps.primitiveClass 常量引导方法(condy)拿到 Class 对象,签名由 PrimitiveGenerator 拼装——编译器在为原始类型模式生成 invokedynamic 与常量池素材。另有 L510:选择子是原始类型时豁免 null 检查(原始类型无 null);L935 的绑定模式 null 检查按类型是否原始分两支。口径边界:exactness、dominance、exhaustiveness 的完整规则只从 JEP 文档取得,Attr.java、Check.java 里的编译期实现不在本文范围。
2.3 JEP 533:结构化并发,一个 sealed 接口加一台三态状态机
结构化并发把一组相关任务当作单一工作单元:子任务默认跑虚拟线程;fork/join/close 只能由 owner 线程调用,违反抛 StructureViolationException;一个子任务失败即短路取消其余;owner 被中断则关闭作用域、取消全部子任务;子任务继承 ScopedValue;JSON thread dump 展示任务层级树(以上运行时行为均为 JEP 口径)。本版五点改动(JEP 533 History):接口与 Joiner 增加第三类型参数 R_X(join() 抛出的异常类型);新增 open(UnaryOperator);allSuccessfulOrThrow() 等三个工厂让 join() 抛 ExecutionException 并各增带 Function 的重载;移除 Joiner.awaitAll();onTimeout() 被 timeout() 取代,超时异常以 CancelledByTimeoutException 为 cause。官方示例(JEP 533 原文):
try (var scope = StructuredTaskScope.open()) {
Subtask<String> user = scope.fork(() -> findUser());
Subtask<Integer> order = scope.fork(() -> fetchOrder());
scope.join();
return new Response(user.get(), order.get());
}
源码所见(StructuredTaskScope.java,全文件 1469 行,实现类 StructuredTaskScopeImpl)。L373–421:
public sealed interface StructuredTaskScope<T, R, R_X extends Throwable>
extends AutoCloseable
permits StructuredTaskScopeImpl {
sealed interface Subtask<T> extends Supplier<T> permits StructuredTaskScopeImpl.SubtaskImpl {
enum State { UNAVAILABLE, SUCCESS, FAILED }
两个 sealed 钉死结构:scope 只 permit 包私有的实现类,Subtask 只 permit SubtaskImpl。孵化期这个 API 住在 jdk.incubator.concurrent,JDK 27 的正式位置是 java.base 的 java.util.concurrent——从孵化器毕业到预览,模块归属是演进路标。Subtask 状态机只有三态,且 extends Supplier<T>,get() 即取结果,没有 Future 那套杂面。Joiner 默认方法(L572–618)把状态纪律写进代码:onFork 要求子任务必须 UNAVAILABLE,onComplete 要求不能还是 UNAVAILABLE,两道断言框死回调里能见到什么。open 工厂的三层默认(L1268–1269):零参 open() 走 Joiner.awaitAllSuccessfulOrThrow()——任一子任务失败即整体失败,javadoc(L1173–1176)写明默认配置创建未命名虚拟线程、无超时。全文件公开 API 均挂 @PreviewFeature(STRUCTURED_CONCURRENCY)——源码层面再次确认它仍是预览 API。口径边界:虚拟线程创建点、取消传播的中断调用、timeout 定时机制都在实现类里,本文不引用其内部代码。
三、技术评估
证据形态先摆清楚:源码引文全部来自 jdk-27+35 tag;性能数字全部来自 JEP 官方页面引用,属官方口径,非独立复测。
JEP 534:机制可证,收益信官方。 机制层面,位布局注释、位域常量、默认开关三处源码互相咬合,压缩方案在代码里完整自洽,这是硬证据。收益层面,官方引用数字:某场景 SPECjbb2015 堆占用少 22%、CPU 时间少 8%;另一场景 GC 次数减 15%(G1 与 Parallel 均如此);一个高并行 JSON 解析基准快 10%。背书同为 JEP 自述:Amazon 数百个生产服务在用(多数 backport 到 JDK 21/17),SAP 的 SapMachine 已默认开启。风险写得明白:为 Valhalla 预留 4 bits,不够可再压类指针与 identity hash code(JEP 450 口径)。冷水:klass_bits 写死 22,类空间寻址上限被进一步压缩——用位宽换默认开启的明确取舍。
JEP 532:语义稳定,仍是预览。 第 5 次预览、且本版相对 JDK 26(JEP 530)无改动再预览,信号是语义基本收敛;但「第 5 次」本身说明没转正,语法仍有调整空间,生产大规模铺开有返工风险。实现层面,两段式降译加 condy 取 Class 说明它在编译器内部不轻——原始类型进模式匹配,代价是 javac 多生成一层中间结构。
JEP 533:API 面在收敛。 七次预览里改动最频的部分——构造方式、完成策略、超时机制——到 JDK 27 收成 R_X 加 open(UnaryOperator) 加 timeout() 三个动作,典型收敛期形态。sealed 接口收实现、状态机砍到三态,设计克制看得见。冷水:第 7 次预览意味着未承诺永久兼容,取消传播与超时的内部机制在实现类里,评估只能停在接口语义层。
合起来看。 成熟度阶梯清晰:534 升级即得;532、533 要开预览开关,都不该进要求长期稳定的关键路径。三者都不引入新概念——紧凑头是位复用,原始类型模式匹配是把 widening 补进模式,结构化并发是把结构化原则搬进并发——工程主义的另一面:没有惊喜,也没有魔法。
四、价值判断
真问题都是真的:对象头开销在 64 位堆上常年被诟病;instanceof 与 switch 排斥原始类型是语言一致性的旧伤口;并发代码的错误处理与取消传播是 Java 程序员事故率最高的区域之一。三个 JEP 各对准一个真问题。
AI 写代码越来越多,人读源码的价值反而在涨,轻点一句:大量代码由模型生成之后,人省下的时间花在哪——性价比极高的去处是往下读一层,看编译器把新语法变成什么字节码、运行时在对象头上动了哪几位。编译器和运行时不会因为你没读过就变简单,语言特性越多,「源码里实际发生什么」与「你写的代码」距离越远。expr instanceof int 被改写成绑定模式加与运算的两段式,就是例子:只看语法糖以为是一次测试,读了 TransPatterns 才知道它是一次类型回收加一次运行时 exactness 判定。
省内存对 AI 服务部署有直接意义,但别夸大:JVM 上的推理服务、检索服务、Agent 网关,堆占用每降一截云上账单就省一截,对象头砍掉三分之一对高对象密度服务是确定的利好方向;但「方向」不等于「你的服务就是那 22%」,收益取决于对象大小分布与分配速率。
边界写清楚:532、533 是预览,JDK 28 可能再调甚至再预览,正式代码等转正;534 默认启用但可回退,与监控工具、原生 agent 出兼容性问题时回退键还在;本文性能与背书信息均为官方口径,引用时保持同一口径。什么时候值得追:维护 JVM 服务、关心内存账单、写并发密集代码的人,现在就开 --enable-preview 把 532、533 摸熟。什么时候不该追:要求 API 永久稳定的关键生产路径,等转正。
五、如何落地
开关。 JDK 27 已 GA(2026-09-15,build 35)。JEP 534 默认开启无需参数,LP64 生效、32 位恒关,用 -XX:+PrintFlagsFinal 查 UseCompactObjectHeaders 应为 true。JEP 532、533 是预览,编译运行两端都要 --enable-preview(javac 加 --release 27),缺一端直接被拒。
跑示例。 2.2、2.3 节的官方示例是最小入口。建议跑两个变体:把 case int i 换成 case long i 看 dominance 报错;把 Joiner.anySuccessfulOrThrow() 换成默认 open() 看失败传播差别。亲手试一遍胜过读十遍规则。
读源码。 两条路:装好的 JDK 自带 lib/src.zip,解开即有 java.base 与 jdk.compiler 源码;HotSpot 的 C++ 要走 GitHub openjdk/jdk,checkout tag jdk-27+35,四个文件直接抄:src/hotspot/share/oops/markWord.hpp(看 L40–76 注释、L116–154 常量)、src/hotspot/share/runtime/globals.hpp(L131 开关)、src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java(L200–221、L772–828)、src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java(L373–1468)。读法:先看头注释与 javadoc——OpenJDK 注释密度极高,位布局图、变换规则都写在注释里——再带问题跳实现,不要逐行硬啃。
六、如何自己做一套类似的方案
这个栏目的「自己复现」换到 JDK 上,最现实的路径是从读源码走到给 OpenJDK 提一个小补丁。OpenJDK 是巨型工程,但对个人贡献者并非高墙,分四段。
第一段,读源码打底。 按第五节的四个文件入手,吃透头注释后顺藤摸瓜:markWord.hpp 往下读 oops 层级,TransPatterns.java 往上读 TreeTranslator 与同目录的 Attr、Check,StructuredTaskScope.java 直连实现类。目标不是全懂,是建立「改一个文件会影响谁」的直觉。
第二段,本地构建。 openjdk/jdk 自带构建系统,类 Unix 平台 ./configure 后 make images。首次全量构建一到两小时,之后增量编译几十秒。能本地构建才算拿到实验台。
第三段,跑测试再改代码。 OpenJDK 用 jtreg:javac 测试在 test/langtools,HotSpot 在 test/hotspot/jtreg,JDK API 在 test/jdk。正确姿势是先写失败的新用例(预览特性尤其欢迎补边界用例),再改代码让它通过。
第四段,提交流程。 常识性工序:风格与提交格式用仓库自带 jcheck;变更以 webrev 形式挂到组件评审列表由 Committer 评审;首次贡献前签署 OCA(Oracle Contributor Agreement),电子签署,以官方贡献者页面为准。落点选择:532、533 处预览期,javadoc 措辞、错误提示、边界测试这类低风险改进是真实且受欢迎的入口;HotSpot 的位布局与开关文档同样常年接受小修。具体归属与导师制度以各项目页当前说明为准。
压缩成一张图:
读源码(markWord.hpp / TransPatterns.java / StructuredTaskScope.java)
→ ./configure && make images(本地构建 JDK 27 镜像)
→ jtreg 跑目标组件测试(test/langtools、test/jdk……)
→ 先写失败用例,再改实现,再跑绿
→ jcheck → webrev 挂评审 → OCA(首次)→ Committer 合并
难度分布诚实:前两段熟手两周内完成,第三段起考验组件理解,第四段的评审往返最长。好处也是别处没有的:你改的每一行,会跑在明年全世界数亿个 JVM 上。
结论
JDK 27 的三个特性是同一枚硬币的三面:JEP 534 把对象头从 96 bits 压到 64 bits,markWord.hpp 的位图与 globals.hpp 的默认开关证明它是成熟工程,收益以官方口径为准;JEP 532 让 instanceof 与 switch 支持全部原始类型,TransPatterns 的两段式降译把运行时 exactness 判定搭了出来,第 5 次无改动再预览意味着语义收敛;JEP 533 把结构化并发收进 java.base,sealed 接口、三态状态机、awaitAllSuccessfulOrThrow 默认策略在源码里一目了然。三个都不是新范式,全是旧账还清。升级即得 534,另外两个开 --enable-preview 摸熟,等转正再上生产。
参考来源
- JEP 534: Compact Object Headers by Default(openjdk.org/jeps/534;Owner: Roman Kennke;Closed/Delivered, Release 27)
- JEP 450: Compact Object Headers (Experimental)(openjdk.org/jeps/450;紧凑布局位图与压缩机制细节)
- JEP 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview)(openjdk.org/jeps/532;Owner: Angelos Bimpoudis;规则体系与官方示例)
- JEP 533: Structured Concurrency (Seventh Preview)(openjdk.org/jeps/533;Authors: Alan Bateman, Viktor Klang, Ron Pressler;API 形态、五点改动与官方示例)
- openjdk/jdk 源码(GitHub,tag jdk-27+35):markWord.hpp(L40–76、L116–154)、globals.hpp(L131–132、L150)、TransPatterns.java(L200–221、L510、L772–828、L935)、StructuredTaskScope.java(L373–1468)
- JDK 27 项目页 JEP 清单(openjdk.org/projects/jdk/27,「JDK 27 reached General Availability on 15 September 2026」)