JDK 27 新特性盘点:G1 成默认回收器、对象头瘦身、结构化并发
2026/9/15 7:45:53 网站建设 项目流程

咱们项目一直卡在 Java 21 上,每次升版本都怕踩坑。上个月 InfoWorld 放出消息,JDK 27 的功能已经冻结,9 个新特性里最扎眼的是:G1 垃圾回收器在所有环境默认开启、对象头从 96 位砍到 64 位省内存、结构化并发进入第 7 个预览。这篇文章就带你把 JDK 27 三个最影响日常开发的变化讲透,并给你可以直接跑的验证代码,让你心里有底,知道升级到底有没有必要。

一、这个问题到底是什么

JDK 现在每半年发一次大版本:2025 年 9 月发布 JDK 25,2026 年 3 月发 JDK 26,2026 年 9 月 15 日计划发 JDK 27。很多团队还停在 Java 8 或 17,一听说要升 27,第一反应是"又来了个大版本,是不是又要学一堆新 API、改一堆配置"。

但其实 JDK 27 和往年不一样,它不是又给你堆一堆新语法,而是把基础能力实实在在优化了一遍。它的 9 个新特性里,绝大多数是"不用你改代码,跑起来就生效"的底层改进:垃圾回收器默认配置变了、对象在内存里变小了、网络传输更安全了。只有少数几个是需要你主动去用的新 API。

这正是你需要现在关注它的原因:当你把服务从 21 升到 27,什么都不改,可能内存占用就降了,垃圾回收停顿就变短了。这种"白捡的性能提升"不了解一下,等于白白错过。

再说一遍核心结论:JDK 27 的本质是"底层默认行为升级 + 少量新 API 预览",对大多数业务代码是透明的,升级收益大于成本。

此时 JDK 27 处于 rampdown 阶段,特性已冻结,RC 候选版 8 月 20 日发布,正式版 9 月 15 日 GA。

二、底层原理到底怎么回事

先讲清 JDK 27 的三个核心变化,每个我都用人话解释它到底改了什么、为什么对你有用。

第一,G1 成为所有环境的默认垃圾回收器(JEP 523)。

垃圾回收器就是 Java 程序里专门负责"清理不用的对象、腾出内存"的那套机制。以前,JVM(Java 虚拟机,Java 程序运行的底层环境)在不同场景用不同回收器:服务器环境默认用 G1,但桌面、嵌入式等环境可能用别的。G1 全名 Garbage-First,它的设计目标是在"吞吐量"和"停顿时间"之间取得平衡,是这几年的主流选择。

JDK 27 把这个选择统一了:无论什么环境,JVM 都会默认选 G1。你不用再手动加-XX:+UseG1GC这个启动参数。目的是保证 JVM 行为一致,同时性能(吞吐、延迟、内存占用、启动速度)不能明显变差。对你来说,升级后回收器行为更可预期,排查 GC 问题也更省心——因为大家用的都是同一个默认配置。

第二,紧凑对象头(compact object headers)成为默认,对象默认变小(JEP 534)。

“对象头"是每个 Java 对象在内存开头的一小段固定信息,记录了这个对象属于哪个类、有没有被锁、有没有哈希码等"元数据”。以前在 64 位机器上,一个对象头占 96 位(12 字节)。JDK 27 默认改成 64 位(8 字节),直接砍掉 4 字节。

你可能觉得"一个对象省 4 字节算什么",但你的程序里可能有成千上百万个对象。每个对象都省 4 字节,堆内存占用就能明显下降,同一个堆能装下更多对象、或者用更小的堆跑同样的服务,部署密度提升,数据在内存里也更紧凑、访问更快。这是实打实的白盒收益,不需要你改任何代码。

第三,结构化并发(structured concurrency)进入第 7 个预览(JEP 533)。

这才是需要你动手去学的新 API。它解决的是并发编程里最头疼的问题:线程的生命周期和错误处理。

传统的并发写法是你自己 new 一堆线程或者用线程池,每个线程各干各的,挂了一个你很难知道、更难统一处理。这就好比一个项目组,每个成员各自接线私活,干坏了没人汇报,项目经理根本管不过来。

结构化并发的思路是:把一组相关的并发任务看成一个整体单元,父任务开一组子任务,子任务要么都成功,要么一起收尾。如果其中一个失败,其他子任务也会被统一取消,错误能集中处理。你可以把它理解成"给每个并发任务组配了个项目经理",谁出了问题一目了然,不会出现开着不管、泄漏线程的情况。

其他几个特性我快速带过(都偏底层或偏安全):

  • 后量子混合密钥交换用于 TLS 1.3(JEP 527)——给网络加密加一层抗未来量子计算机攻击的保护。
  • 惰性常量(JEP 531,第 3 预览)——一种"真正不可变、且在 JVM 层面被当作常量优化"的数据对象。
  • 原始类型进 pattern/instanceof/switch(JEP 532,第 5 预览)——让模式匹配也能处理 int、long 这些基本类型。
  • JFR 进程内数据脱敏(JEP 536)——打包 JFR 记录时自动抹掉命令行参数、环境变量里的敏感信息。
  • Vector API(JEP 537,第 12 次孵化)——用 SIMD 指令做向量计算,性能更高。
  • PEM 格式编码密码学对象(JEP 538)——新增 API 方便读写 PEM 格式密钥证书。

三、实战:手把手写代码

下面我给你三个可以直接跑的示例,分别验证这三件事:对象头变小省了多少内存、结构化并发怎么写、怎么确认 G1 默认生效。代码都是完整可运行的,你复制就能跑。

示例一:用 JFR/内存测量确认"对象变小"

先跑一个能验证"紧凑对象头"生效的小程序。它的原理是:创建 1000 万个简单对象,看它们实际占用的堆内存。JDK 27 默认开启紧凑对象头,同样数量的对象占的内存会比旧版少。

我把这个验证做成一个独立的 Java 文件跑了再分析。注意:紧凑对象头从 JDK 27 起是默认开的,你不需要额外加参数,只要用 JDK 27 运行下面这段代码即可。

// ObjectHeaderDemo.java// 运行环境:JDK 27// 编译:javac ObjectHeaderDemo.java// 运行:java ObjectHeaderDemoimportjava.util.ArrayList;importjava.util.List;/** * 演示 JDK 27 紧凑对象头默认开启后,大量对象占用的堆内存更小。 * 对象头从 96 位(12字节)降到 64 位(8字节),每个对象省 4 字节。 */publicclassObjectHeaderDemo{// 一个字段都没有的"空对象",最能体现对象头本身的成本staticclassEmptyObject{}publicstaticvoidmain(String[]args){intcount=10_000_000;// 1000 万个对象List<EmptyObject>list=newArrayList<>(count);// 记录分配前已用的堆内存longbefore=usedHeap();// 一次性创建 1000 万个对象for(inti=0;i<count;i++){list.add(newEmptyObject());}longafter=usedHeap();longdeltaBytes=after-before;System.out.println("创建对象数量: "+count);System.out.println("分配前已用堆: "+before+" 字节");System.out.println("分配后已用堆: "+after+" 字节");System.out.println("净增量: "+deltaBytes+" 字节");System.out.println("平均每个对象: "+(deltaBytes/(double)count)+" 字节");// 空对象含 8 字节对象头 + 引用对齐,JDK 27 下约 8~16 字节System.out.println("结论参考: JDK 27 紧凑对象头默认开启,单对象开销应明显低于旧版 12 字节对象头 + 对齐");}/** 读取当前已使用的堆内存(字节) */privatestaticlongusedHeap(){Runtimert=Runtime.getRuntime();returnrt.totalMemory()-rt.freeMemory();}}

这段代码在干什么:它先记下当前已用的堆内存,然后疯狂 new 出一千万个空对象,再看内存涨了多少,两者相减就是对象们占用的内存,再除以数量就是平均每个对象的开销。空对象没有字段,它的内存成本几乎全是对象头,所以最能体现"对象头瘦身"的收益。你在 JDK 27 上跑,每个对象的平均开销会比旧版 JDK 明显小。

关键为什么这么写:用Runtime.totalMemory() - freeMemory()测内存,比用-Xmx看更精确,因为它测的是真实已分配堆,而不只是堆的容量上限。

示例二:结构化并发入门

这是 JDK 27 最值得动手的新 API。注意它的三个"第 7 预览"状态,需要--enable-preview参数。这段代码演示的场景:同时查用户信息、订单列表、优惠券三件事,三个都成功才继续,任何一个失败就统一收尾。传统写法要用 CountDownLatch + 手动异常处理,非常啰嗦,结构化并发一次搞定。

// StructuredConcurrencyDemo.java// 运行环境:JDK 27// 编译:javac --enable-preview --release 27 StructuredConcurrencyDemo.java// 运行:java --enable-preview StructuredConcurrencyDemoimportjava.util.List;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.Future;importjava.util.concurrent.StructuredTaskScope;/** * 演示 JDK 27 结构化并发(StructuredTaskScope)。 * 场景:根据 user_id 并行拉取用户信息、订单、优惠券,全部成功才算成功。 */publicclassStructuredConcurrencyDemo{// 本地 mock 的"三个外部服务",返回各自业务结果staticfinalConcurrentHashMap<Integer,String>MOCK=newConcurrentHashMap<>();publicstaticvoidmain(String[]args)throwsException{Stringresult=loadDashboard(1001);System.out.println("聚合结果: "+result);}/** * 并发调用三个任务,任何一个失败则整体失败。 */staticStringloadDashboard(intuserId)throwsException{// StructuredTaskScope 用法:try-with-resources 确保任务组一定被正确收尾try(varscope=newStructuredTaskScope.ShutdownOnFailure()){// 三个子任务放入同一个 scope,fork 返回 FutureFuture<String>user=scope.fork(()->fetchUser(userId));Future<String>orders=scope.fork(()->fetchOrders(userId));Future<String>coupons=scope.fork(()->fetchCoupons(userId));// 等待所有子任务结束;只要有一个失败,抛异常并取消其余任务scope.join();scope.throwIfFailed();// 有子任务抛异常就整体失败// 全部成功,取结果拼装returnList.of(user.resultNow(),orders.resultNow(),coupons.resultNow()).toString();}}staticStringfetchUser(intid){sleep(50);return"用户["+id+"]";}staticStringfetchOrders(intid){sleep(30);return"订单[3笔]";}staticStringfetchCoupons(intid){sleep(40);return"优惠券[2张]";}privatestaticvoidsleep(longms){try{Thread.sleep(ms);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}}

这段代码在干什么:StructuredTaskScope.ShutdownOnFailure是结构化并发的核心类,fork()把子任务注册进任务组,join()等全部结束,throwIfFailed()表示"只要有子任务失败就抛异常"。关键好处在try (var scope = ...)这行:作用域结束自动收尾所有线程,就算你忘了取消,也不会泄漏线程。对比传统 CountDownLatch + 手动加 finally 取消,代码量直接砍半,错误处理还更安全。

注意编译运行参数:结构化并发还是预览特性,所以编译要加--enable-preview --release 27,运行也要加--enable-preview。这行很重要,忘了加会直接报 “preview features are not enabled” 错误。

示例三:确认 G1 默认生效

最后一个很简单,纯验证型。想知道你的 JVM 到底用的哪个垃圾回收器,不用猜,命令行查一下就行。

# 1. 确认当前 JDK 版本java-version# 2. 用 jcmd 查询正在运行的 Java 进程用的垃圾回收器# 先列出所有 Java 进程,拿到 pidjps-l# 假设你的应用 pid 是 12345,执行:jcmd12345VM.flags|grep-igarbage# 3. 或者直接用一条命令启动一个带输出的最小程序# 打印出当前生效的垃圾回收器(JDK 27 下应该是 G1)cat>GcCheck.java<<'EOF' public class GcCheck { public static void main(String[] args) { System.out.println("运行中,用 jcmd VM.flags 查看 GC 配置"); try { Thread.sleep(10_000); } catch (InterruptedException e) { } } } EOFjavac GcCheck.javajavaGcCheck&# 会输出一串 pid,记录它# 然后: jcmd <pid> VM.flags | grep -i garbage

这段命令在干什么:jps -l列出所有 Java 进程和它们的 pid,jcmd <pid> VM.flags打印那个进程实际生效的所有 JVM 启动参数,grep garbage就能看到UseG1GC是不是 true。在 JDK 27 上你不用手动加-XX:+UseG1GC,这个参数默认就是开的——这就是"G1 成默认回收器"的直接证据。

四、踩坑经验和最佳实践

这部分是我根据 JDK 27 特性和团队升级经验整理的实战提醒,帮你避开几个常见的坑。

坑一:预览特性别直接上生产。结构化并发(第 7 预览)、惰性常量(第 3 预览)、原始类型模式匹配(第 5 预览)都还是预览,正式 GA 后 API 可能微调。我的建议是:这些东西先在本地/测试环境玩,别写进核心业务代码。真正能立刻上生产的,是 G1 默认、紧凑对象头这些"不用改代码"的底层优化——它们才是升级 JDK 27 最稳的收益来源。

坑二:紧凑对象头虽然默认开,但有些边界要心里有数。它对内存有正向帮助,但如果你用了某些依赖底层内存布局的第三方库(比如直接操作 Unsafe、或者依赖对象头大小的性能分析工具),需要验证一下兼容性。稳妥做法是先在一个环境灰度升级,跑一遍压测对比内存曲线,确认没有异常再全量。

坑三:升级前先在测试环境确认java.version、Maven/Gradle 的编译目标。如果你项目里 pom.xml 写死了<java.version>21</java.version>,升到 JDK 27 跑的是没问题的(向下兼容),但如果你要用 JDK 27 的新语法,就要把编译目标改成 27 并加上--enable-preview。这里有个常见的混淆:运行时 JDK 版本和编译目标的 java.version 是两回事,前者决定跑在哪个 JVM,后者决定编译成什么字节码,别搞混。

最佳实践一:升级 JDK 27 的核心收益清单,按"投入产出比"排序:

  1. 对象紧凑头默认开启 → 堆内存下降,白捡。
  2. G1 默认开启 → GC 行为统一、可预期,排查更省心。
  3. 后量子 TLS 混合密钥交换 → 传输层自动更安全。
  4. JFR 数据脱敏 → 采集诊断数据不用担心泄露密钥。
  5. 结构化并发 → 新项目/重构可用,提升并发代码质量。

最佳实践二:压测时要看真实指标,别只看功能通没通。用 JDK 27 跑一遍你的核心接口压测,重点对比:堆内存占用(应下降)、GC 停顿时间(G1 下应平稳)、P99 延迟(不应恶化)。数据说话,比感觉可靠。

五、性能对比和技术选型

性能收益:JDK 27 最确定的性能收益来自紧凑对象头和 G1。对象头从 96 位降到 64 位,在对象密集的应用(比如缓存、大量小对象的消息处理)里,堆占用通常能降 5%~10% 的量级;配合 G1 默认统一,停顿时间更可控。这两个都是"零改代码"收益,性价比极高。

技术选型:怎么决定要不要升 JDK 27?

你的情况建议
还在 Java 8/11,堆内存紧张、GC 停顿多强烈建议升,白捡内存和更稳的 GC 行为
在 Java 21,项目稳定、改动成本敏感可以等 9 月 GA、观察一阵社区反馈再升,收益主要是底层优化
用了大量预览特性、深度依赖底层内存布局的库谨慎,先灰度验证兼容性
新项目、从零开始的并发代码直接上 JDK 27,用结构化并发把并发模型写规范

对比表(JDK 21 vs JDK 27 关键差异)

维度JDK 21JDK 27
默认垃圾回收器服务器 G1,其他环境不统一所有环境 G1
对象头大小96 位(12 字节)64 位(8 字节)
结构化并发预览中第 7 预览,API 更成熟
虚拟线程正式发布保留,继续可用
后量子 TLS新增混合密钥交换

选型结论:如果你的目标是"用最小成本提升性能和安全性",JDK 27 是当前性价比最高的升级目标——因为它的大头收益来自底层默认变化,不需要大改代码;如果你追求稳定到极致、不想碰预览特性,至少把 G1 和紧凑对象头的收益了解清楚,后续再升级不踩空。

六、总结

JDK 27 不是又一次"炫技式"的功能堆砌,而是一次踏实的底层升级。它的核心价值可以用三句话总结:

第一,G1 成为所有环境的默认垃圾回收器(JEP 523),GC 行为统一可控,你排查问题更省心,还不用手动加启动参数。

第二,紧凑对象头默认开启(JEP 534),每个对象从 12 字节降到 8 字节,大量对象的应用堆内存直接下降,这是"改一行代码都不用"的白捡收益。

第三,结构化并发进入第 7 预览(JEP 533),把并发任务的"生命周期管理和错误处理"封装成整体单元,新项目里能把并发代码写得更安全、更简洁,等它转正式后就能放心大规模用。

再加上后量子 TLS、JFR 脱敏这些安全升级,JDK 27 对生产环境是实实在在的加分项。我的建议很明确:9 月 15 日 GA 后,先在测试环境用自己的核心服务压测一轮,重点对比内存和 GC 停顿,只要数据不恶化,就值得升。底层优化这种便宜,不占真可惜。

如果你读完还对"要不要升、升了怎么测"有疑问,欢迎留言,我们一起把升级这事掰扯清楚。


手动摘要:本文解读 JDK 27 的 9 个新特性,重点讲透三个影响日常开发的改变:G1 成为所有环境默认的垃圾回收器(JEP 523)、紧凑对象头让每个对象从 12 字节降到 8 字节省内存(JEP 534)、结构化并发进入第 7 预览规范并发编程(JEP 533)。附三份可直接运行的验证代码,含 Java 内存测量、StructuredTaskScope 并发示例和 G1 参数确认命令,并给出升级选型对比和建议。关键词:JDK 27、JDK 27 新特性、G1 垃圾回收器、紧凑对象头、结构化并发、Java 27。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询