Java 27 升级实测:默认值动得比新特性多,有个老参数会让 JVM 直接起不来

昨天刷到 JDK 27 GA 的消息(build 35 转正,Mark Reinhold 在邮件列表发的公告),看了眼 JEP 列表:9 个 JEP,4 个 preview 加 1 个 incubator,没什么能上头条的大特性。搁往年这种版本我可能扫一眼就过了,但这回翻 release notes 的时候发现不对劲------这版的默认值动得有点多,对象头、GC、JFR 全都在悄悄换行为。 今天把 Linux x64 的 GA 包(jdk.java.net/27,226MB)拉下来跑了一遍,java --version 出来是这样:

java 复制代码
openjdk 27 2026-09-15
OpenJDK Runtime Environment (build 27+35-2325)
OpenJDK 64-Bit Server VM (build 27+35-2325, mixed mode, sharing)

下面这些结果都是真跑出来的,不掺水。

对象头默认瘦了 8 个字节

这版我最关心的是 JEP 534(Compact Object Headers by Default)。这个特性 JDK 25 就进来了,但得手动加 -XX:+UseCompactObjectHeaders 才生效,升到 27 默认就开。 原理一句话:64 位平台上对象头从 96 位压到 64 位,12 字节的头部变 8 字节。听着像抠门优化,但 Java 应用里对象多啊,官方 release notes 说整体堆占用大概能降 20%。 官方数字归官方,我自己写了个最简单的例子实测:new 两百万个 Object 存进数组,固定 768MB 堆,各跑三遍取稳定值:

ini 复制代码
默认(compact headers 开):usedHeap=24MB
-XX:-UseCompactObjectHeaders:usedHeap=40MB

200 万个对象差 16MB,算下来每个对象省 8 字节,正好是头部从 12 字节加 4 字节 padding 变成 8 字节的账。当然我这例子是极端情况,全是小对象,真实业务里混着大对象会稀释这个比例,官方说的 20% 更接近常态。 顺带说个测的时候翻的车:第一版代码把数组写成局部变量,结果 JIT 逃逸分析直接把对象分配消掉了,两组数字都是 3MB,白测一轮。想复现的记得把 holder 放到 static 字段去。 有个连带影响要单独说:UseCompressedClassPointers 被废弃了,拿旧脚本试了一下:

ini 复制代码
OpenJDK 64-Bit Server VM warning: Ignoring option UseCompressedClassPointers; support was removed in 27.0

注意措辞是 Ignoring,JVM 能起来,但配置管理平台要是还在统一下发这个参数,每次启动都刷一条警告,看着心烦。建议升级前先清一遍参数清单。

G1 把 Serial 的活也接了

JEP 523 讲的是"让 G1 在所有环境当默认 GC"。以前 JVM 在资源受限的环境(比如限制了几百 MB 内存的小容器)会自动退回 Serial GC,现在不会了,只要没显式指定就一律 G1。用 PrintFlagsFinal 看默认值:

ini 复制代码
bool UseG1GC        = true   {product} {ergonomic}
bool UseSerialGC    = false  {product} {default}
bool UseParallelGC  = false  {product} {default}
bool UseZGC         = false  {product} {default}

我手里有几个跑在 1C512M 小容器里的工具类服务,按以前的选择逻辑默认就是 Serial,升级后会被切到 G1。G1 自己要维护记忆集,还得跑并发线程,内存开销比 Serial 高一截,在特别小的容器里这部分占比不小。官方的说法是 G1 这几年打磨得够好,即便资源受限也不该比 Serial 差多少------"should not degrade significantly",原文就这么写的。 不过我没做正经压测,不敢替官方打包票。要是有服务跑在极限小内存容器里,升级前最好拿真实流量回放一遍,盯一下 GC 开销和 RSS。这种环境差异,官方测得再全也不如你自己的一组数据实在。

有个老参数会让 JVM 直接起不来

升级 27 最容易炸的就是这类旧参数。release notes 里列了一串被移除的选项,我挑了两个最容易踩的实测。 先试 -Xverify:none。老项目关字节码校验提速启动的祖传偏方,我在不少老代码仓库的启动脚本里都见过它。实测结果:

vbnet 复制代码
$ java -Xverify:none -version
Unrecognized verification option: -Xverify:none
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

直接拒绝启动,连 -version 都跑不了。谁要是把它写在 Dockerfile 的 ENTRYPOINT 或者 K8s 的启动命令里,升级完容器直接 CrashLoop,而且报错信息跟"参数过期"对不上号,得反应一会儿才想起来是它。 相对温和的是 IHOP 改名:-XX:InitiatingHeapOccupancyPercent 改成 -XX:G1IHOP,旧参数现在只是警告:

arduino 复制代码
OpenJDK 64-Bit Server VM warning: Option InitiatingHeapOccupancyPercent was deprecated
in version 27.0 and will likely be removed in a future release. Use option G1IHOP instead.

能跑,但按这个节奏,下个版本它就是下一个 -Xverify:none。手里有 JVM 调优参数的服务,这波最好一起改掉。另外 -noclassgc-noverify-verifyremote 也一起被移除了,都在同一份 release notes 里,升级前建议把启动参数全量 grep 一遍。

JFR 开始自动帮你抹敏感信息

JEP 536 是个容易被忽略但挺贴心的变化:JFR 录制里的命令行参数、环境变量、系统属性,遇到疑似敏感的键名会自动打码再落盘。我起了个带"密码"的系统属性做录制:

ini 复制代码
$ java -Ddb.password=SuperSecret123 -Dapp.token=abc12345 \
    -XX:StartFlightRecording=filename=rec.jfr JfrApp

录完用 jfr print 看这个事件:

ini 复制代码
key = "db.password"
value = "[REDACTED]"

键名还在,值没了。我又在整个录制文件里 grep 明文,一个都搜不到。默认过滤规则覆盖了 password、token、secret、credential 这类常见键名,也能用 -XX:FlightRecorderOptions:redact-key 加自定义规则。以前 JFR 文件随手扔给同事排查问题是常事,这心理负担算是小了一圈。

预览特性里 Lazy Constants 值得盯一下

9 个 JEP 里有 4 个 preview、1 个 incubator,大部分还在反复打磨,跑下来我觉得 Lazy Constants(JEP 531,第三次预览)是里面最实用的。 它解决的是个老矛盾:final 字段必须构造时就赋值,想延迟初始化就得用可变字段加判空,JVM 又没法对可变字段做常量折叠。LazyConstant 把"延迟初始化"和"不可变"合并了:

rust 复制代码
static final LazyConstant<String> DB_URL = LazyConstant.of(() -> {
    System.out.println("[trace] loading db config from remote...");
    return "jdbc:postgresql://prod.db.internal:5432/app";
});

跑出来的效果:

arduino 复制代码
app started, config not loaded yet
[trace] loading db config from remote...
connect: jdbc:postgresql://prod.db.internal:5432/app
connect again: jdbc:postgresql://prod.db.internal:5432/app

第一次 get() 才加载,第二次直接用缓存值,trace 只打了一次。官方文档明确说多线程下也保证只算一次,JVM 还能把它当真常量做优化。Spring 的 @Lazy 用了这么多年,现在 JDK 层面给了个标准件。当然它还是 preview,编译运行都得加 --enable-preview,编译器也会提醒一句 "uses preview features of Java SE 27",生产别急着上,测试环境先玩玩。

另外几个快速带过:结构化并发(JEP 533,第七次预览)我用 StructuredTaskScope.open() 开 scope、fork 两个子任务再 join,跑通了基本流程;原始类型模式(JEP 532)让 v instanceof int 和 switch 里的 case int 成为可能,实测能编译能跑;Vector API 第 12 次进 incubator,从 JDK 16 算起五年没转正,这个耐心我是真服气;后量子 TLS 1.3 混合密钥交换(JEP 527)和 PEM 编码(JEP 538)跟多数业务开发关系不大,知道有这回事就行。

升级前就干三件事

生产项目求稳,25 LTS 还是首选,27 是非 LTS 版本。但你要是本来就打算跟非 LTS,27 值得升------省内存是白捡的,G1 全面接管对多数服务无感。真正要动手的就两件事:把启动脚本里的 -Xverify:none 清了,参数清单里被废弃的项顺手改掉。要是还有服务跑在极限小容器里,升级完多观察一眼 GC,别全信"should not degrade significantly"。

写完这篇顺手翻了眼手头几个老项目的启动脚本,心里有点虚------JVM 参数都有几年没动过了,回头得挨个 grep 一遍再升级。

相关推荐
Bs_MoneyMagnet1 小时前
基于springboot+vue的个人健康管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·vue3·springboot3·计算机毕业设计
GreenTea1 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持1 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
niucloud-admin1 小时前
JAVA V6 多商户商城 开发文档——job 计划任务开发
java·python·github
海宇AI2 小时前
零信任架构实战:基于海宇柠檬查出险-登记证构建自动化车抵贷核保网关
java·人工智能·架构·自动化
Bs_MoneyMagnet4 小时前
基于springboot+vue的心理咨询预约与随访平台的设计与实现 源码+文档
vue.js·spring boot·后端·spring·毕业设计·旅游·计算机毕业设计
cfm_29144 小时前
单例模式详解
java
我叫张土豆4 小时前
本体论、DDD、SDD、TDD:AI 编程时代的四层认知栈
java·人工智能
IT_陈寒4 小时前
Vue的响应式更新把我坑惨了,原来问题出在这
前端·人工智能·后端