JDK 27 新特性速览:这 9 个变化会影响你的代码

发布时间:2026/8/6 1:26:34
JDK 27 新特性速览:这 9 个变化会影响你的代码 如果你的项目还在用 JDK 17这篇文章你得看完。JDK 27 明天8 月 6 日发布 RC 版本9 月 15 日正式 GA。虽然它是一个短期支持版本非 LTS但这次带了 9 个 JEP其中 G1 成为唯一默认 GC、结构化并发接近正式版、对象头从 96 位压缩到 64 位——每一个都直接关系到你线上服务的性能和内存开销。这篇文章不列清单挑 5 个对你影响最大的特性讲清楚它到底改了什么、对你的代码意味着什么、你现在该不该为此做点什么。写作日期2026 年 8 月 5 日基于 JDK 27 EA Build 33 OpenJDK JEP 文档。一、G1 成为全场景默认 GC你的小容器可能变快了这是 JDK 27 影响面最大的一个变化。从 JDK 9 开始G1 已经是服务器环境至少 2 个 CPU 1792MB 内存的默认 GC。但在小规格环境单 CPU 或内存低于 1792MB下JVM 会自动退回到 Serial 收集器。JDK 27 把这个限制拿掉了。无论多少 CPU、多少内存只要你不手动指定 GCJVM 就选 G1。为什么现在可以这么干OpenJDK 团队在 JEP 523 里给出了数据支撑经过 JDK 9 到 27 的多轮优化包括 JEP 522 对 G1 同步机制的削减G1 在小规格环境下的吞吐量已经接近 Serial而延迟始终优于 Serial——因为 G1 用增量回收代替了 Serial 的全堆 Stop-The-World。具体来说Serial 在旧一代回收时必须做 Full GC此时所有工作线程全停。而 G1 的 Mixed GC 只回收一部分 Region停顿时间可控。对你的影响如果你在跑 Docker 容器——比如一个 512MB 内存、单核的服务——以前它用的是 Serial GC升级到 JDK 27 后会自动切到 G1。大多数场景下这是好事延迟会更稳定。但要注意一点G1 的内存占用比 Serial 略高需要记住 Region 的 Remembered Set如果你的容器内存卡得特别紧比如 256MB 以下建议做一下基准测试对比。如果你本来就手动指定了 GC比如-XX:UseZGC或-XX:UseParallelGC这个变化跟你没任何关系——显式指定的优先级永远高于默认值。怎么验证# 升级前看看当前用的什么 GCjava-XX:PrintCommandLineFlags-version21|grepGC# JDK 27 上不加任何 GC 参数输出应该包含 -XX:UseG1GC二、结构化并发第七次预览离正式版又近一步结构化并发JEP 533是从 JDK 19 就开始孵化的特性经历了 7 个版本迭代API 已经相当成熟。它跟虚拟线程是天生一对——虚拟线程让你创建百万级轻量线程结构化并发让你管理这些线程的生命周期。核心思想传统的并发代码用ExecutorServiceFuture线程的父子关系完全靠你自己用代码维护。一个任务 fork 出两个子任务如果其中一个失败了另一个得手动取消——漏写一行 cancel那个线程就飘在那里直到超时或服务重启。结构化并发把这个问题反过来解决任务树是结构化的父任务的生命周期天然包含子任务。父任务结束时所有子任务要么完成、要么被取消不存在孤儿线程。// JDK 27 的结构化并发 APIPreviewtry(varscopeStructuredTaskScope.open(config-config)){SubtaskStringuserTaskscope.fork(()-fetchUser(userId));SubtaskListOrderorderTaskscope.fork(()-fetchOrders(userId));// join() 等待所有子任务完成任一个失败则取消其他scope.join();// 两个任务都成功了StringuseruserTask.get();ListOrderordersorderTask.get();returnnewDashboard(user,orders);}对比传统写法 vs 结构化并发传统写法——线程池 Future容易写出泄漏代码// 传统写法子任务失败后另一个子任务可能没人取消线程泄漏ExecutorServiceexecutorExecutors.newVirtualThreadPerTaskExecutor();try{FutureStringuserFutureexecutor.submit(()-fetchUser(userId));FutureListOrderorderFutureexecutor.submit(()-fetchOrders(userId));StringuseruserFuture.get();// 如果这里抛异常...ListOrderordersorderFuture.get();// 这个 Future 没人管了}finally{executor.close();// 粗暴关掉可能有正在跑的任务被中断}结构化并发——try-with-resources 帮你自动清理// 结构化并发作用域结束时保证所有子任务状态确定try(varscopeStructuredTaskScope.open(config-config)){SubtaskStringuserTaskscope.fork(()-fetchUser(userId));SubtaskListOrderorderTaskscope.fork(()-fetchOrders(userId));scope.join();// userTask 或 orderTask 任一失败另一个自动被取消returnnewDashboard(userTask.get(),orderTask.get());}// 离开 try 块时scope 保证没有活着的子线程关键差异传统写法里异常和取消的关系是你记住结构化并发里是JVM 保证。JDK 27 的变化这次预览对 API 做了几处收尾式调整StructuredTaskScope现在有第三个类型参数R_X控制join()可以抛什么异常——不再是一股脑抛ExecutionException新增StructuredTaskScope.open()静态方法作为最简洁的入口Joiner工厂方法的异常处理更灵活可以通过Function自定义包装异常什么信号已经第七次预览了API 收敛到这个程度说明 OpenJDK 团队对设计方向很满意。结构化并发大概率在 JDK 28 或 29 转正。如果你的项目已经在用虚拟线程JDK 21现在就可以在 JDK 27 上试用结构化并发配合--enable-preview参数。它是虚拟线程的拼图的最后一块——你有了轻量线程、有了结构化生命周期管理就不需要再手写ExecutorServiceCompletableFuture的组合拳了。三、紧凑对象头每个 Java 对象省 32 位内存这是 JDK 27 的一个沉默但重磅的改动。说它沉默是因为你不需要改任何代码——JVM 自动生效。说它重磅是因为线上内存占用直接减少。改了什什么在 64 位架构上HotSpot JVM 中每个 Java 对象的对象头Object Header占用 96 位Mark Word64 位存 GC 信息、锁状态、哈希码等Klass Pointer32 位指向类元数据开启压缩指针后紧凑对象头JEP 534把 Mark Word 从 64 位压到 32 位。最终对象头变成 64 位32 Mark Word 32 Klass Pointer每个对象节省 32 位 4 字节。听起来不多算一笔账一个典型的微服务应用堆里可能有 500 万个活跃对象。4 字节 × 500 万 20MB。而且这还没有算上对象的对齐填充减少带来的额外节省——对象大小变小了对齐浪费也变少了。技术原理Mark Word 的 64 位里很多位在大多数时候是冗余的。比如一个没被锁住、没被 GC 标记的对象它的 Mark Word 里只有哈希码和 GC age 是有用的。JEP 534 把这些信息重新压缩编码到 32 位同时保持与现有锁机制和 GC 算法的兼容性。你不需要改代码不需要改 JVM 参数升级 JDK 后自动启用。想关掉可以加-XX:-UseCompactObjectHeaders但除非遇到极特殊的兼容性问题没理由关。对你的影响所有 Java 应用升级到 JDK 27 后免费获得内存节省。对于内存敏感的容器部署场景这可能是把 OOM 推迟的关键。对于大数据量的缓存服务堆里对象特别多效果尤其明显。四、Lazy Constants延迟初始化终于有官方支持了痛点// 这种代码你写过多少次privatestaticfinalExpensiveObjectCACHE;static{CACHEExpensiveObject.loadFromDisk();// 启动慢得要死}static final字段在类加载时就初始化了。如果你的对象初始化代价高读文件、连网络、解析大 XML应用启动时间就被拉长。而volatile 双重检查锁定又容易写出 bug而且编译器不能对volatile变量做常量折叠优化。JDK 27 的方案// 延迟常量代码上声明为 final行为上等第一次访问时才初始化privatestaticfinalLazyConstantExpensiveObjectCACHELazyConstant.of(ExpensiveObject::loadFromDisk);LazyConstant是线程安全的——多线程同时访问时保证只初始化一次。而且 JVM 把它当成真正的常量对待可以做常量折叠、内联等优化性能跟static final字段一致。这次是第三次预览API 进一步精简删掉了isInitialized()和orElse()两个低层方法新增Set.ofLazy(...)工厂方法现在 List、Set、Map 三种集合都有懒初始化版本了LazyConstant vs volatile DCL传统的双重检查锁定DCL// 传统 DCL 单例易错、编译器不优化privatestaticvolatileExpensiveObjectinstance;publicstaticExpensiveObjectget(){if(instancenull){synchronized(ExpensiveObject.class){if(instancenull){instancenewExpensiveObject();}}}returninstance;}LazyConstant 一行搞定// JDK 27 LazyConstant线程安全、JVM 可做常量折叠优化privatestaticfinalLazyConstantExpensiveObjectCACHELazyConstant.of(ExpensiveObject::loadFromDisk);DCL 的问题不只是代码丑——volatile阻止了 JIT 编译器做常量折叠和内联优化。LazyConstant被 JVM 识别为真正的常量编译期和运行期优化都能生效性能优于 DCL。适用场景配置文件解析、字典加载等启动时不需要但运行时会用到的数据单例模式的正统替代——不用手写 DCL微服务启动优化——把非关键的初始化从启动路径上挪走无状态工具类中大对象的懒加载正则 Pattern、Jackson ObjectMapper 等五、其余 5 个 JEP 速览不是每个 JEP 都对普通开发者有直接影响。剩下 5 个按重要性排序JEP名称一句话影响范围532原始类型模式匹配switch和instanceof支持int/long/double等原始类型第五次预览日常编码语法糖向527后量子 TLS 密钥交换TLS 1.3 支持抗量子计算攻击的混合密钥交换算法金融、政务等安全敏感场景536JFR 进程内数据脱敏JFR 录制时自动遮盖命令行参数、环境变量中的敏感信息运维/安全537Vector APISIMD 向量计算第 12 次孵化高性能计算、ML 推理538PEM 编码 API密钥和证书的 PEM 编解码标准 API预览加解密、证书管理六、你该做什么如果现在用的是 JDK 21 LTS不用急着升级JDK 27 不是 LTS。但可以在非生产环境试试结构化并发 虚拟线程的组合提前适应代码风格。下一个 LTS 大概率是 JDK 29 或 30结构化并发转正的可能性很大。如果现在用的是 JDK 17 LTS你已经落后了。JDK 17 的生命周期到 2027 年还有段时间但 JDK 21 的虚拟线程、JDK 27 的紧凑对象头和 G1 默认化这三个东西加起来对你的线上服务提升是实实在在的。建议直接跳到 JDK 21 LTSJDK 27 作为下一个 LTS 的预演版本了解一下就行。如果你是 Spring Boot 用户确认你用的 Spring Boot 版本兼容目标 JDK。Spring Boot 4.1.0 官方支持 JDK 21跑在 JDK 27 上需要验证——一般向后兼容没问题但建议先在 CI 里跑一遍完整测试。一个具体的测试建议找一台非核心服务、内存配置较低的容器比如 1 核 512MB升级到 JDK 27把 G1 和紧凑对象头的组合效果跑一遍基准测试。你会发现 GC 停顿更平、内存占更小。这两个变化可能是你这轮升级里收益最确定的。