前提说明
— Java 25 是最新的 LTS 版本(于 2025 年 9 月发布)
— 许多特性在 Java 25 是“稳定(production)”级别,有的仍然是“预览 / incubator / experimental”级别。
— 升级时应充分评估兼容性、回退路径、是否关闭预览特性、性能回归风险等。
下面我先列出 Java 25 相对于 Java 21 的主要改进/新增特性,然后对几个核心特性用代码示例说明。
一、Java 25 相比 Java 21 的主要优化 / 新特性
| 类别 | 新特性 / 优化 | 状态(稳定 / 预览 / 实验 / 孵化) | 动机 /价值 | Java 21 是否具备 / 差异 |
|---|---|---|---|---|
| 语言 / 语法简化 /可读性 | Compact Source Files & Instance main 方法(JEP 512) | 稳定 | 减少样板代码,降低入门门槛,快速原型 | Java 21 有 “unnamed class + instance main (preview)” 功能 (JEP 445) |
| 语言 / 模块管理 | Module Import Declarations(JEP 511, 预览) | 预览 | module 级别 import 声明,增强模块可读性 | Java 21 模块依赖只能写在 module-info 中 |
| 语言 / 模式匹配 | 在 pattern / instanceof / switch 中支持原始类型(JEP 507,第三预览) | 预览 | 统一模式匹配处理引用类型与原始类型,减少拆箱/装箱 /冗余代码 | Java 21 支持对引用类型做模式匹配/switch 匹配(但不支持原始类型) |
| 语言 / 构造器灵活性 | Flexible Constructor Bodies(JEP 513) | 稳定 | 允许在 super() 或 this() 调用之前写语句(但不能引用未初始化的 this) | Java 21 无此支持 |
| 语言 / 值 / 变量 | Scoped Values(JEP 506) | 预览 | 替代 ThreadLocal 的一种轻量级机制,尤其适用于虚拟线程上下文数据传递 | Java 21 虚拟线程已经是特性之一(JEP 444) |
| 性能 / 启动 / JIT / 提前优化 | Ahead-of-Time Method Profiling | 稳定 | 利用历史执行数据(方法调用频率/热度)来优化 JVM 启动和“时间到高峰性能”(time-to-peak) | Java 21 没有内建这种跨启动的性能剖析 / 预测机制 |
| 性能 / 内存 /对象布局 | Compact Object Headers | 稳定 / 实验 (depending on platform) | 缩减对象头部占用,从而减少堆内存使用、提高缓存效率 | Java 21 使用传统对象头(通常 12 或 16 字节) |
| 垃圾收集 / GC | Gen Shenandoah / 更好 class-loading & linking 优化 | (部分实验 /演进) | 改进 GC 延迟 / 分代支持,减少类加载 / 链接开销 | Java 21 在 GC / class-loading 上已有成熟方案,但 Java 25 在这些路径上继续优化 |
| 可观察性 / 性能剖析 | JFR 的 CPU 时间剖析 (JEP 509) / 方法级追踪 / 合作式采样改进 | 实验 / 可选开启 | 提高性能分析的粒度、降低剖析开销 | Java 21 的 JFR 支持传统采样剖析、事件记录等,但没有精细的 CPU-time 方法级测量 |
| 安全 / 加密 | Key Derivation Function API(JEP 510) | 稳定 | 标准化 KDF(如 HKDF)接口,简化密钥派生操作 | Java 21 没有内建统一的 KDF API,可能依赖第三方或自定义实现 |
| 加密 / PEM 编码 | PEM 编码 / 解码密钥 / 证书 (JEP 470) | 预览 | 方便在标准 PEM (Base64 + header/footer) 格式下操作密钥 / 证书 | Java 21 缺乏标准 API 支持 PEM 编码 / 解码 |
| 架构 / 平台清理 | Remove 32-bit x86 Port | 稳定 | 删除对 32 位 x86 Windows 平台的支持(减小维护负担) | Java 21 已弃用 /准备移除 32-bit x86 支持 |
此外还有少数孵化 /实验性特性如 Vector API 的进一步演进(JEP 508 incubator),以及与 Project Leyden /优化启动/链接/Class Loading 相关的增强(例如提前类加载 / 链接支持)等。
二、重点特性及示例
2.1 Primitive Types in Patterns / switch / instanceof (JEP 507,第三预览)
介绍
在 Java 21 中,模式匹配(pattern matching for instanceof, switch)主要支持引用类型(对象类型)。如果是原始类型(int, long, float, double 等),仍然需要用传统的 if/else 或 switch + 显式比较。这样在一些需要处理基本类型的业务逻辑中,仍然存在冗余和拆箱开销。
JEP 507 的目标是让 pattern 匹配机制也适用于基本类型,从而统一语法风格、消除不必要的拆箱、提高表达力。
示例对比
假设我们有一个通用方法 handle(Object obj),要根据 obj 是哪一种基本类型进行分类:
在 Java 21 中,你可能写:
void handle(Object obj) {
if (obj instanceof Integer) {
int i = (Integer) obj;
System.out.println("int: " + i);
} else if (obj instanceof Long) {
long l = (Long) obj;
System.out.println("long: " + l);
} else if (obj instanceof String) {
String s = (String) obj;
System.out.println("string: " + s);
} else {
System.out.println("other");
}
}
有两点不优雅:一个是对基本类型的装箱/拆箱,另一个是代码样板较多。
在 Java 25(启用该预览特性)可以写:
void handle(Object obj) {
if (obj instanceof int i) {
System.out.println("int: " + i);
} else if (obj instanceof long l) {
System.out.println("long: " + l);
} else if (obj instanceof String s) {
System.out.println("string: " + s);
} else {
System.out.println("other");
}
}
这里 instanceof int i 是一个新语法,意味着 “如果 obj 是一个 Integer(或可被视为 int 的包装类型或拆箱表达式)” 的匹配,并将其解包为基本类型 i。这个抽象层使得逻辑更统一、更简洁。
示例2,用 switch + pattern:
static void process(Object o) {
String result = switch (o) {
case int i -> "int " + i;
case long l -> "long " + l;
case String s -> "string " + s;
default -> "other";
};
System.out.println(result);
}
以前 switch 中不能写 case int i,但在 Java 25 的预览中支持。这样你可以把对基本类型的判断也融入 switch 匹配语法,从而提升可读性。
注意 / 限制
-
这个特性目前仍然处于预览阶段(需显式打开预览支持)。
-
在升级过程中,预览语法可能还会调整,需要考虑未来兼容性风险。
-
与泛型、自动拆箱、可空性等组合使用时,可能有边界情况要仔细测试。
2.2 Flexible Constructor Bodies(JEP 513)
介绍
传统 Java 语言要求在构造器中必须先调用 super(...) 或 this(...) 构造子构造器,再写其它语句。也就是说,你不能在 super(...) 之前做一些预计算(例如参数校验、中间变量计算等)。这在一些场景下显得有些不灵活。
Flexible Constructor Bodies 放宽这一限制:允许在调用 super(...) 或 this(...) 之前写一定“安全”的语句(这些语句不能引用 this 的实例方法或字段,只能做一些局部计算、分支判断等)。这能让构造函数更加灵活,减少传参的复杂性或冗余检查代码。
示例
假设有一个类设计如下:
class Person {
private final String name;
private final int age;
public Person(String name, int age) {
super(); // 必须是第一条,这在 Java 21 是强制的
if (name == null) throw new NullPointerException("name");
if (age < 0) throw new IllegalArgumentException("age negative");
this.name = name;
this.age = age;
}
}
有时候你想基于传入参数先做一些派生计算或预检查,甚至根据某些条件决定要调用另一个构造函数:
在 Java 25 的 flexible constructor bodies 中,你可以写:
class Person {
private final String name;
private final int age;
public Person(String name, int age) {
// 在 super(...) 之前写语句:
if (name == null) throw new NullPointerException("name");
if (age < 0) throw new IllegalArgumentException("age negative");
// 然后调用 super 或 this
super();
// 或者 this(name, ageAdjusted) 等
this.name = name;
this.age = age;
}
public Person(String name) {
this(name, 0);
}
}
之前这种写法在 Java 21 是不允许的,必须先写 super(...)。新规则让你在调用 super() 之前写“安全”的检查或预计算逻辑,从而构造器的表达力更强。
注意 / 限制
-
新写的语句不能依赖
this(不能调用this.xxx、访问字段、调用实例方法),因为对象还未构造完全。 -
仍然要保证初始化流程安全、早期校验逻辑不会破坏不变式。
-
对于复杂继承体系,使用这种特性时要谨慎,避免引入难以理解的构造顺序问题。
2.3 Compact Source Files & Instance main 方法 (JEP 512)
介绍
Java 传统上对初学者 / 快速原型编写有一定门槛:必须写 class、public static void main、包声明、import 等。这些样板代码(boilerplate)对写一个“Hello World / 快速实验”程序显得冗长。Compact Source Files 的目标是让 Java 在轻量脚本 / 教学 /原型阶段更接近脚本语言的便利性。
示例
在 Java 25 引入的 compact source / instance main 功能下(稳定特性):
void main() {
IO.println("Hello, World!");
}
甚至可以不写 String[] args 参数,直接写 void main(),而且你可以使用 IO.println(...) 这样的简便方式(IO 是一个内建的工具类)。
2.4 Ahead-of-Time Method Profiling & 启动 / warm-up 优化
介绍
对于现代服务和云原生应用,启动时间(cold start)和“到达稳定状态的时间”(time-to-peak)非常关键。传统 JVM 启动后依靠运行时的 JIT 编译、热点探测、方法内联、代码剖析回馈等机制逐步优化性能,这意味着一个服务冷启动后可能经过一段“温暖期”才达到最佳性能。这在短生命周期、无服务器 (serverless)、容器 / 微服务频繁重启的环境中是痛点。
Java 25 引入的 AOT (Ahead-of-Time) method profiling 机制允许 JVM 利用历史执行数据(比如上一次运行或部署期间的方法热度)来预先影响 JIT / 内联 / 优化策略,从而缩短 warm-up 阶段,提高启动和性能稳定性的表现。
示例讨论
假定你有一个微服务,每次部署启动后会被调用一次核心接口 compute()。在 Java 21 中,第一次几个调用可能会慢,因为 JIT 还没将其内联 / 优化好。
在 Java 25 中,你可以预先记录 compute() 方法在之前一次部署的执行频率 /热度,并将这个 profile 信息作为启动输入,让 JVM 在初始化阶段就为 compute() 做优化准备。这样第一次调用就可能相对更快。
从概念层面可以理解为:
-
运行期间收集 method hotness 数据(方法被频繁调用、频率统计等)。
-
保存为 profile 文件。
-
启动 JVM 时带入这个 profile 文件(命令行参数、JVM 启动参数、配置机制等)。
-
JVM 在类加载 / JIT 编译阶段就参考这些 profile 信息,优先优化这些热点方法。
这个机制在短生命周期的服务 / 容器化部署 /冷启动场景中有较大意义。
2.5 Compact Object Headers(对象头压缩优化)
介绍
在 Java 堆中,每个对象除了其字段数据之外,还携带 “对象头”(object header),通常包含元数据、类型指针、锁 / GC 状态 bits 等。在经典 HotSpot 实现中,对象头(在 64 位 JVM 上)常常占用 12 或 16 字节(视对齐 /压缩指针 /标记等机制而定)。当系统存在大量小对象时,对象头开销成为整体内存占用的不可忽视部分。
Java 25 引入 compact object headers 优化,目的是在特定平台 /场景下压缩对象头大小,从而节省堆内存、改善缓存命中率、降低 GC 负担。
具体效果(如一些 benchmark 报告)包括:对象头从原始 12 或 16 字节压缩到例如 8 字节,从而在某些场景下可以减少约 20%-22% 的 heap 使用。InfoWorld
注意 / 兼容性
-
这个特性在不同 JVM 实现 / 不同硬件 /不同指针压缩策略下可能表现不同。
-
在大型对象 /数组 /混合对象场景下,对象头本身可能不是主要瓶颈,收益视具体业务模型而定。
-
需要在性能测试环境中验证是否带来真实收益/副作用(GC 行为、对齐、内存碎片等)。
2.6 JFR CPU-Time Profiling / 方法级追踪 / 可观察性增强 (JEP 509)
介绍
Java Flight Recorder (JFR) 是 Java 平台自带的轻量级事件记录 / 性能剖析工具。传统 JFR 支持采样式剖析、事件记录、堆 / GC /线程 /锁 / I/O 等事件。但它主要基于 wall-clock 采样(即按时间间隔采样线程状态),而在某些场景下你更希望按 CPU 时间(即线程实际消耗的 CPU 时间)来进行更精细的剖析,尤其在混合执行(比如有 I/O、休眠、阻塞、内核 /本地调用混杂)场景下,wall-clock 模型可能掩盖瓶颈。
JEP 509 在 Java 25 引入了 CPU-time profiling(实验级别),使得 JFR 能按方法级别记录线程真实消耗的 CPU 时间,从而帮助你把“时间花在哪些方法上”看得更清楚。InfoWorld+2InfoQ+2
此外还做了 cooperative sampling 优化、方法级追踪改进等,使得在生产环境中剖析的开销更低、准确性更高。InfoWorld+1
示例 / 使用方式(伪代码 / 配置方式)
-
启动 JVM 时开启 CPU-time profiling(根据文档加相应 JVM 参数)。
-
在 JFR 输出中,会有新的事件类型 / 方法级记录项,例如每个方法的 CPU 时间分布、调用图、热点方法等。
-
你可以用 Java Mission Control (JMC) 或其他工具打开这些 events,定位 CPU hotspot,而不是 “wall-clock hotspot”。
例如:
# 假设 JVM 参数类似:
-XX:StartFlightRecording=...,settings=profile-cpu-time.jfc
然后在输出的 .jfr 文件里,你会看到新的字段 /事件报告,说这个方法用了多少 CPU 时间。你可以对比不同版本 /不同负载下的 CPU-time 分布差异。
这个增强让性能定位、瓶颈分析的精度和效率提升,尤其是在复杂业务、多层调用栈、异步 /阻塞混合的系统中非常有价值。
2.7 Scoped Values(JEP 506)
介绍
在 Java 21 以后,虚拟线程(virtual threads, JEP 444)已成为语言层的可选特性,用于实现更轻量级的并发模型(类似协程)。
传统上,在线程上下文中共享数据、透传上下文(如请求上下文、事务标识、日志追踪 ID 等)是通过 ThreadLocal 完成的。但 ThreadLocal 在虚拟线程 + 大并发 +频繁挂起/恢复的环境下可能开销较大、语义不够清晰。
Scoped Values 提供一种更轻量、语义更明确的替代方式:你可以在某个作用域 (scope) 内定义一个值,该值能在当前线程 /协作线程上下文中透传给子任务 /挂起再恢复的代码,而不依赖 ThreadLocal 的全局存储。它旨在与虚拟线程组合使用,使得上下文透传更加高效、安全。InfoQ+2jrebel.com+2
示例
伪代码示例(基于预览特性):
class MyService {
static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
void handleRequest(Request req) {
String rid = req.getId();
try (var scope = ScopedValue.where(REQUEST_ID, rid)) {
// 在这个作用域内,子任务 /方法可以通过 REQUEST_ID.get() 获取 rid
process();
}
}
void process() {
// 在同一线程 /作用域上下文内,可以访问:
String rid = REQUEST_ID.get(); // 不再需要 ThreadLocal.get()
log("Processing, requestId=" + rid);
// 启动子虚拟线程 /异步任务也可以继承这个作用域(视 API 设计)……
}
}
与 ThreadLocal 相比好处:
-
更明确的作用域界定(生命周期由
try (scope)控制) -
在虚拟线程上下文中可能更高效、开销更小
-
语义更清晰,不容易造成内存泄漏 / ThreadLocal 污染
限制 /注意
-
目前是预览特性(需显式开启)
-
与现有 ThreadLocal 混用时要谨慎,可能会有迁移成本
-
在异步 /回调 /线程池 /跨线程传输等复杂场景中,要验证上下文传播语义是否满足业务需求
三、总结
3.1 潜在优势(升级动机)
-
更快启动 / 更快“时间到性能峰值”
— 利用 AOT method profiling,可以缩短冷启动 /warm-up阶段,尤其对短生命周期服务 /自动伸缩 /容器化部署场景有意义。
— 更好的类加载 / 链接优化 + 在内部增强启动路径的机制能改善整体启动性能。 -
内存效率 /对象开销优化
— compact object headers 带来的对象头压缩,可以在大量小对象场景下减少堆内存占用、提高缓存效率。
— 更轻量的上下文透传机制(Scoped Values)比 ThreadLocal 更高效 /语义更好。 -
更强的可观察性 /性能剖析能力
— JFR 的 CPU-time profiling + 方法级追踪 + 改进的采样机制让你在生产环境中以更低开销获得更精细的性能数据。
— 这意味着诊断 / 调优 /回归分析能力提升。 -
语言 /表达力 /可维护性提升
— 原始类型的模式匹配、flexible constructor bodies、compact source 文件等语法糖让业务代码更简洁、意图更清晰、样板更少。
— 这些改进在长期项目里减少“样板/模板/重复”代码的维护成本。 -
加密 /安全 /标准库增强
— 内建统一的 KDF API, PEM 编码/解码支持,让加密 /密钥派生 /证书处理更标准、安全、便捷。
— 有助于在越来越多需要加密 /后量子密码学 (PQC) 的环境中减少自造轮子。 -
平台清理 /未来可持续性
— 移除 32-bit x86 支持减少维护负担。
— 推动 JVM /语言进一步现代化,为后续更激进的优化/Project Valhalla /Project Panama 等扫清路径。
3.2 风险 /挑战
-
预览 /实验特性的不稳定性
— 像 primitive-in-pattern, Module Import, Scoped Values 等还处于预览 /实验阶段,未来有可能修改语义或 API。升级时要有回退计划。
— 在生产项目中,默认应禁用预览特性,除非经过充分验证。 -
兼容性 /代码回归风险
— 语言语法层面的变化可能引入隐蔽的兼容性问题(例如重载解析、模式匹配冲突等)。
— 必须在升级后做全面回归测试(单元测试、集成测试、性能压力测试)。 -
性能回归 / GC /对象模型副作用
— 虽然 compact object headers、GC 优化等在理论上提升性能 /内存效率,但在某些业务模式 /对象布局 /内存碎片 /对齐策略下可能反而带来不可预料的副作用。
— 必须在真实负载下进行观察和对比。
— 对于启动 /Warm-up 优化机制,若 profile 数据与实际运行环境差异太大,也可能产生次优 JIT 策略。 -
配置 /调优 /监控 /工具链适配
— 新特性(如 JFR CPU 时间剖析、profile 注入机制、Scoped Values 等)需要你在部署 /运维层面调整启动参数、监控工具支持、日志 /剖析工具升级等。
— 如果你团队已有成熟的性能监控 /剖析 /追踪方案,升级时要考虑与旧方案的协同 /切换成本。 -
迁移渐进策略
— 对于大型系统,可能不能一次性全量升级。应采取分阶段、Canary /灰度 /模块拆分策略。
— 在迁移过程中,部分模块仍可能运行在 Java 21,同时还要保证跨版本兼容(例如 RPC / 序列化协议 / class 兼容性等)。
— 在初期可以只启用稳定特性,待成熟后,才逐步考虑预览 /实验特性。

497

被折叠的 条评论
为什么被折叠?



