Java 25 新特性详解与实战示例
Java 25 已于 2025 年 9 月 16 日正式发布,作为一款长期支持(LTS)版本,它在核心 Java 库、语言规范、安全性及性能等领域带来了大量增强。甲骨文(Oracle)计划为 Java 25 提供至少 8 年的支持服务,这意味着企业可以灵活安排迁移节奏,同时享受到最新特性带来的优势------包括更强大的 AI 能力与更高的开发者效率。
作为最新的 LTS 版本,Java 25 在语言语法、API、安全、性能与监控等方面均实现了重大升级,让 Java 对各类使用者而言都更易用、更强大。所有新特性均通过 JDK 增强提案(JEP)引入,因此下文将针对每个特性,依次介绍其对应的 JEP、核心升级内容,并提供实用的代码示例或应用场景。
目录
-
JEP 507:模式匹配、instanceof 与 switch 支持基本类型(第三次预览)
-
JEP 512:紧凑源文件与实例 main 方法
-
JEP 513:灵活的构造函数体
-
JEP 511:模块导入声明
-
JEP 505:结构化并发(第五次预览)
-
JEP 506:作用域值(Scoped Values)
-
JEP 502:稳定值(Stable Values,预览)
-
JEP 510:密钥派生函数 API
-
JEP 470:加密对象的 PEM 编码(预览)
-
JEP 508:向量 API(第十次孵化)
-
JEP 519:紧凑对象头
-
JEP 521:分代 Shenandoah 垃圾回收器
-
JEP 514:提前编译命令行易用性优化
-
JEP 515:提前编译方法分析
-
JEP 503:移除 32 位 x86 端口
-
JEP 509:JFR CPU 时间分析
-
JEP 518:JFR 协作式采样
-
JEP 520:JFR 方法计时与追踪
1. JEP 507:模式匹配、instanceof 与 switch 支持基本类型(第三次预览)
该特性的目标是实现"统一数据探索"------允许类型模式支持所有类型(无论是基本类型还是引用类型)。它最初由 JEP 455(JDK 23)提出,后经 JEP 488(JDK 24)第二次预览且未做修改,在 JDK 25 中以预览特性形式第三次推出,内容仍保持不变。
如今 Java 的模式匹配已支持基本类型,这不仅简化了代码,还减少了错误。开发者可直接对基本类型进行类型安全的匹配与解构,无需多余的装箱操作或冗长代码;针对基本类型的模式分支,也让 switch 和 instanceof 的用法更简洁,能更直观地实现基本类型的模式匹配。
实战示例
示例 1:switch 匹配基本类型
java
Object obj = 42;
switch (obj) {
case int i -> System.out.println("基本类型 int:" + i);
case double d -> System.out.println("基本类型 double:" + d);
default -> System.out.println("其他类型");
}
示例 2:带条件判断的 switch 基本类型匹配
java
Integer code = -1;
switch (code) {
case int n when n > 0 -> System.out.println("正整数:" + n);
case int n when n < 0 -> System.out.println("负整数:" + n);
default -> System.out.println("零");
}
上述代码避免了不必要的强制转换与装箱/拆箱操作,逻辑更清晰。
示例 3:instanceof 匹配基本类型
java
Object obj = 10;
if (obj instanceof int val) {
System.out.println("基本类型 int 值:" + val);
} else if (obj instanceof double val) {
System.out.println("基本类型 double 值:" + val);
}
这里的核心突破是:instanceof 不再只支持引用类型,还能直接匹配 int、double 等基本类型。它省去了手动拆箱与类型转换的模板代码------在早期 Java 版本中,开发者需要编写显式的转换逻辑;而现在,只要对象兼容(例如 Integer 对应 int、Double 对应 double),匹配就能成功,且基本类型变量可直接使用。这种方式在处理泛型类型或装箱值时,能提升类型安全性与代码可读性。
2. JEP 512:紧凑源文件与实例 main 方法
该特性最早在 Java SE 21 中以 JEP 445"无名类与实例 main 方法(预览)"的形式推出,后在 Java SE 22、23、24 中持续预览,如今在 Java 25 中正式定稿,并更名为"紧凑源文件与实例 main 方法"。
本次发布的核心更新如下:
-
基础控制台 IO 类包路径调整 :核心控制台输入输出类
IO
已迁移至java.lang
包,这意味着所有 Java 源文件无需手动导入,就能直接使用该类。 -
紧凑源文件的 IO 方法调用规则 :在紧凑源文件中,
IO
类的静态方法不再默认导入,必须通过类名前缀调用(如IO.println("Hello, world!")
),若想省略类名,则需显式添加静态导入语句。 -
IO 类实现重构 :
IO
类现在作为System.out
和System.in
的包装类存在,替代了之前对java.io.Console
类的依赖。
若需了解该特性在 Java 24 中的状态,可参考 JEP-495 相关文档。
3. JEP 513:灵活的构造函数体
该特性最早在 Java SE 22 中以 JEP 447"super(...) 前允许执行语句(预览)"推出,后在 Java SE 23、24 中持续预览,如今在 Java 25 中正式定稿,且未做重大修改。
它允许在构造函数的"序言部分"(显式调用构造函数 super(...)
或 this(...)
之前)执行语句。开发者现在可在调用父类构造函数前,完成输入验证、计算操作或调用工具方法------但需注意,在此上下文仍无法引用"正在构造的对象"(不能使用 this
,也不能访问未初始化的字段)。
核心能力
-
父类构造前的输入验证:提前过滤非法参数,避免将错误值传入父类。
-
父类构造参数的计算与预处理:可先处理参数,再传递给父类构造函数。
-
更简洁安全的对象构造:在执行昂贵的内存分配前快速失败,减少资源浪费。
限制条件
-
调用构造函数链(
super
或this
)前,不能读取this
的字段或调用实例方法。 -
字段可初始化,但禁止泄露"部分构造对象"的引用(避免其他代码访问未完全初始化的对象)。
-
预构造阶段(super 调用前)允许使用大部分静态方法与本地工具方法。
实战示例
示例 1:防御式编程(参数验证)
java
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
// 先验证参数,再调用父类构造
if (value <= 0) {
throw new IllegalArgumentException("值必须为正数");
}
super(Long.toString(value));
}
}
通过提前验证,避免将非法值传入父类构造函数。
示例 2:条件性调用父类构造
java
public class CustomLogger extends Logger {
public CustomLogger(String config) {
// 先根据配置计算日志级别,再传入父类
String level = "INFO";
if ("debug".equals(config)) {
level = "DEBUG";
}
super(level);
}
}
在调用父类构造前,可动态计算传入参数。
示例 3:调用工具方法预处理
java
public class Metric extends DataPoint {
public Metric(String key) {
// 先调用静态工具方法标准化 key,再传入父类
String normalizedKey = normalizeKey(key);
super(normalizedKey);
}
private static String normalizeKey(String key) {
return key.trim().toLowerCase();
}
}
预构造阶段可调用工具方法处理参数,提升代码复用性。
示例 4:记录(Record)构造函数与 JEP 513
java
public record ValidatedPoint(int x, int y) {
// 非标准记录构造函数
public ValidatedPoint(int x, int y) {
// 先验证参数,再调用默认构造(this(x, y))
checkNonNegative(x, y);
this(x, y);
}
private static void checkNonNegative(int x, int y) {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("坐标不能为负数");
}
}
}
非标准的记录构造函数也能享受这种灵活性,提前完成参数校验。
示例 5:枚举(Enum)构造函数
java
public enum Status {
ACTIVE("A"), INACTIVE("I");
private final String code;
Status(String code) {
// 枚举构造中也能提前验证参数
if (!code.matches("[AI]")) {
throw new IllegalArgumentException("状态码必须是 A 或 I");
}
this.code = code;
}
}
现在可在枚举字段初始化前完成参数验证,避免非法状态码。
4. JEP 511:模块导入声明
该特性最早在 Java SE 23 中预览,后在 Java SE 24 中第二次预览,如今在 Java 25 中正式定稿,且未做重大修改。
它为 Java 语言引入了"模块导入声明",允许开发者通过一条语句导入某个模块中所有导出包的公共类型,语法如下:
java
import module <模块名>;
通过 import module
,开发者可一次性导入一个模块的所有导出类,该特性主要通过以下方式简化开发:
-
简化库的使用:一条语句导入模块所有类型,无需逐个导入包,降低模块化库的使用门槛。
-
减少导入冗余:用一条简洁的模块导入替代多条通配符包导入,减少代码头部的导入语句冗余。
-
降低学习成本:新手无需先了解类的具体包路径,就能直接使用 Java 核心类或第三方库。
-
保持兼容性:模块导入声明可与传统的包导入语句共存,不会产生冲突。
-
支持增量迁移:即使代码库尚未完全模块化,开发者也能通过模块导入享受便利。
关于该特性的详细示例与原理(如模块导入的工作机制、歧义处理、模块系统的传递依赖、java.base
模块的默认可用性、显式依赖 java.se
等),可参考 Java 24 中 JEP-494"模块导入声明(第二次预览)"的文档。
5. JEP 505:结构化并发(第五次预览)
什么是结构化并发?
结构化并发是一种并发编程范式,它将相关的并发任务视为一个"单一工作单元",并为其定义明确的动态作用域。Java 中实现该范式的核心 API 是 StructuredTaskScope
,它能确保:
-
子任务的生命周期被限制在代码块的词法作用域内(通常是 try-with-resources 语句)。
-
错误处理、结果收集与取消操作均能以清晰的方式协同进行。
-
中断的可观测性与传播能力得到增强。
这与"非结构化"线程管理(如 ExecutorService
、CompletableFuture
)形成鲜明对比------在非结构化模式中,任务生命周期难以追踪,错误与取消操作可能无声传播或丢失。
为什么要用结构化并发?
-
简化错误处理:若任一子任务失败,其他子任务会自动取消,避免无效执行。
-
明确的生命周期:所有子任务在同一代码块内启动与结束,防止线程/资源泄漏。
-
协同取消:父作用域的中断会自动传播到所有子任务,确保取消操作的一致性。
-
清晰的结果组合:要么所有任务的结果都可用,要么触发整体失败,避免部分结果无效的情况。
结构化并发的演进历程
该特性的发展贯穿多个 JDK 版本:
-
JDK 19、20:以孵化特性推出。
-
JDK 21:进入预览阶段,
fork
方法的返回类型从Future
改为Subtask
。 -
JDK 22、23、24:持续保持预览状态。
-
JDK 25:第五次预览,核心 API 优化------
StructuredTaskScope
对象现在通过静态工厂方法创建: -
无参的
open()
工厂方法:适用于"等待所有子任务完成或任一子任务失败"的常见场景。 -
接收
Joiner
参数的工厂方法:适用于复杂场景,允许开发者自定义结果处理策略。
实战示例
java
import java.util.concurrent.StructuredTaskScope;
public Response handle() throws InterruptedException {
// try-with-resources 确保作用域自动关闭,资源不泄漏
try (var scope = StructuredTaskScope.open()) {
// 启动两个子任务
var user = scope.fork(() -> findUser()); // 子任务1:查询用户
var order = scope.fork(() -> fetchOrder()); // 子任务2:查询订单
scope.join(); // 等待所有子任务完成,若有异常则传播
// 所有子任务成功,组合结果返回
return new Response(user.get(), order.get());
}
}
若 findUser()
或 fetchOrder()
任一方法抛出异常,另一个子任务会被自动取消,异常会向上传播;离开 try 块时,所有资源会被确保清理。
通过 JEP 505,Java 的结构化并发让并行代码更安全、可读且易于维护------它将子任务分组到明确的生命周期中,确保错误与取消操作的一致性传播;而 StructuredTaskScope
API 是实现这些模式的核心工具,能大幅降低并发代码的出错概率。
6. JEP 506:作用域值(Scoped Values)
作用域值 API 的演进历程如下:JEP 429(JDK 20)中以孵化特性推出,JEP 446(JDK 21)进入预览阶段,后经 JEP 464(JDK 22)、JEP 481(JDK 23)、JEP 487(JDK 24)持续优化,如今在 JDK 25 中正式定稿,仅保留一处小幅修改:ScopedValue.orElse()
方法不再接受 null
作为参数。
什么是作用域值?
作用域值是一种新的并发原语,允许代码在"明确的词法作用域"内,在调用者、被调用者与子线程之间共享不可变的上下文数据。与 ThreadLocal
不同,它的生命周期、可访问性与修改权限均由语言运行时严格管理,避免了手动操作的风险。
为什么不用 ThreadLocal?
ThreadLocal
允许将可变上下文绑定到线程,但存在明显缺陷:
-
上下文生命周期模糊,易导致内存泄漏或数据过期。
-
若不手动移除,
ThreadLocal
值会伴随线程整个生命周期,在虚拟线程、线程池或结构化并发场景中风险极高(例如线程复用导致上下文污染)。
作用域值则完美解决了这些问题:
-
作用域内的上下文会被子线程自动继承,无需手动传递。
-
无需手动清理,值仅在绑定的作用域内可访问,出域后自动失效。
-
值是真正的不可变对象,避免跨请求的意外数据共享。
如何使用作用域值?
1. 声明作用域值
通常声明为 static final
(类似 ThreadLocal
),但仅能在特定作用域内读写:
java
private static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
2. 为作用域绑定值
通过 ScopedValue.where()
方法绑定值,并在 lambda 中执行逻辑:
java
ScopedValue.where(USER_CONTEXT, currentUserContext)
.run(() -> {
processRequest(); // 作用域内的代码可访问 USER_CONTEXT
});
processRequest()
方法及其调用栈中的所有代码(包括子线程),都能通过 USER_CONTEXT.get()
获取绑定的值;出了 run()
块后,USER_CONTEXT.get()
会抛出异常。
实战示例:Web 框架中的上下文传播
传统方式(使用 ThreadLocal)
java
private static final ThreadLocal<FrameworkContext> CONTEXT = new ThreadLocal<>();
void serve(Request request, Response response) {
CONTEXT.set(createContext(request));
try {
Application.handle(request, response);
} finally {
CONTEXT.remove(); // 必须手动清理,否则内存泄漏
}
}
风险:容易忘记清理导致内存泄漏,上下文生命周期模糊,线程复用可能引发数据污染。
作用域值方式
java
private static final ScopedValue<FrameworkContext> CONTEXT = ScopedValue.newInstance();
void serve(Request request, Response response) {
var context = createContext(request);
// 绑定上下文到作用域
ScopedValue.where(CONTEXT, context).run(() -> {
Application.handle(request, response);
});
// 出作用域后,CONTEXT 自动失效,无需手动清理
}
public PersistedObject readKey(String key) {
// 在作用域内读取上下文
var context = CONTEXT.get();
var db = getDBConnection(context);
return db.readKey(key);
}
serve()
方法中绑定的值,仅在 run()
块及其调用栈内可访问;若在 run()
返回后调用 readKey()
,CONTEXT.get()
会直接抛出异常,且无需手动移除值。
示例:ScopedValue.orElse() 方法的变化
Java 25 中,ScopedValue.orElse()
方法的核心修改是:不再接受 null
作为参数,若传入 null
,会直接抛出 NullPointerException
, fallback 值必须非空。
抛出空指针异常的示例
java
import java.lang.ScopedValue;
public class ScopedValueDemo {
private static final ScopedValue<String> SCOPE = ScopedValue.newInstance();
public static void main(String[] args) {
String fallback = null;
String value = null;
try {
// Java 25 中会抛出 NullPointerException
value = SCOPE.orElse(fallback);
} catch (NullPointerException e) {
System.out.println("捕获到预期的空指针异常:fallback 不能为 null");
}
// 正确用法:始终提供非空的 fallback 值
String safeValue = SCOPE.orElse("Default");
System.out.println("安全的 fallback 值:" + safeValue);
}
}
第一次调用 SCOPE.orElse(null)
会抛出空指针异常,正确用法是像 SCOPE.orElse("Default")
这样提供非空 fallback 值------该 API 变更确保了安全性与一致性,开发者在 Java 25 中使用 ScopedValue.orElse()
时,必须遵守"fallback 非空"规则。
正确示例
java
private static final ScopedValue<String> SCOPED_USER = ScopedValue.newInstance();
public static void main(String[] args) {
// 此时 SCOPED_USER 未绑定值
String result = SCOPED_USER.orElse("guest"); // "guest" 是合法的非空 fallback
System.out.println(result); // 输出:guest
}
7. JEP 502:稳定值(Stable Values,预览)
该 JEP 为 Java 25 核心库引入了"稳定值"的预览 API,它是一种支持"延迟不可变性"的新机制。
核心定义与优势
-
本质:用于创建"稳定值"的 API,即数据不会发生变化的对象。
-
性能 :JVM 会像优化
final
字段一样优化稳定值(因为其值是常量)。 -
灵活性 :相比
final
字段,稳定值在赋值时机与方式上更灵活。 -
核心用途:将应用数据的"单一整块初始化"拆分为更小、更高效的阶段,从而提升 Java 应用的启动性能。
-
当前状态:Java 25 中的预览 API,尚未正式定稿。
为什么需要稳定值?
-
传统 final 字段的局限:必须在构造或静态初始化阶段"立即初始化",对于大型应用,这会拖慢启动速度。
-
可变字段的缺陷:支持延迟初始化,但无法享受 JVM 优化,且多线程下的重复赋值或不安全赋值易引发 bug。
稳定值则同时解决了这两个问题:可在后续阶段赋值(仅一次),且仍能享受常量折叠、线程安全等 JVM 优化。
核心 API
核心类为 StableValue<T>
,关键方法如下:
-
StableValue.of()
:创建一个空的稳定值实例。 -
orElseSet(Supplier<T>)
:若值未设置,则通过 Supplier 原子性地赋值(延迟初始化),确保仅一个线程能成功赋值。
实战示例
java
import java.lang.StableValue;
public class OrderController {
// 延迟初始化的不可变 Logger,线程安全且仅赋值一次
private final StableValue<Logger> logger = StableValue.of();
public Logger getLogger() {
// 若未初始化,则通过 Supplier 安全创建,后续调用直接返回
return logger.orElseSet(() -> Logger.create(OrderController.class));
}
}
第一次调用 getLogger()
时,会通过 Supplier
初始化 Logger;后续调用直接返回已创建的不可变实例。
与延迟初始化的对比
传统延迟初始化(无稳定值)
java
private Logger logger = null;
Logger getLogger() {
if (logger == null) {
logger = Logger.create(OrderController.class);
}
return logger;
}
问题:非线程安全(多线程下可能重复赋值),Logger 可被重新赋值,JVM 无法进行常量折叠优化。
稳定值方式(Java 25)
java
private final StableValue<Logger> logger = StableValue.of();
Logger getLogger() {
return logger.orElseSet(() -> Logger.create(OrderController.class));
}
优势:线程安全(原子赋值)、仅赋值一次、JVM 初始化后将其视为常量优化。
进阶示例:启动性能优化
若应用中有多个重量级控制器,可通过稳定值延迟初始化,减少启动时的资源消耗:
java
public class Application {
// 延迟初始化 OrderController 与 UserService
static final StableValue<OrderController> orders = StableValue.of();
static final StableValue<UserService> users = StableValue.of();
static OrderController getOrderController() {
// 仅在首次调用时创建 OrderController
return orders.orElseSet(OrderController::new);
}
}
OrderController
和 UserService
仅在需要时才会被构造,避免启动时的不必要延迟;同时支持并发安全访问,且初始化后变为不可变对象。
特性对比表
| 特性 | final 字段 | Java 25 稳定值 |
|---------------------|---------------------------|--------------------------------|
| 立即初始化 | 必须 | 可选(支持延迟初始化) |
| 引用不可变 | 是 | 是(赋值后不可变) |
| JVM 优化支持 | 是 | 是(赋值后支持) |
| 线程安全 | 是 | 是(原子操作,最多赋值一次) |
| 启动延迟 | 可能存在 | 无(延迟初始化) |
| 预览状态 | 不适用 | 是(Java 25 中为预览) |
优势与最佳实践
-
性能优化:通过延迟初始化减少应用启动时间,无需一次性构建所有对象。
-
线程安全 :
StableValue
确保赋值操作"最多一次",即使多线程并发访问也安全。 -
代码可读性:API 明确标识"延迟初始化但不可变"的字段,逻辑更清晰。
-
兼容性 :JVM 会像处理
final
字段一样对稳定值进行常量折叠优化,确保运行时性能。
8. JEP 510:密钥派生函数 API
该特性为 Java 引入了"密钥派生函数(KDF)"的标准 API。KDF 是一种加密工具,可从现有密钥和附加数据中生成新的安全密钥。
它最早在 JDK 24 中以 JEP 478 作为预览特性推出,如今在 JDK 25 中正式定稿,且未做任何修改。
什么是密钥派生函数(KDF)?
KDF 是一种加密原语,能从主密钥(如密码、主密钥或共享密钥)中派生出一个或多个密钥。它在 TLS 协议、安全密码存储、后量子密码方案等场景中至关重要------接收初始密钥材料、盐值、可选上下文信息等输入,最终生成加密强度足够的密钥。
核心类与方法
-
javax.crypto.KDF
:代表 KDF 算法的核心类。 -
KDF.getInstance(String algorithm)
:根据算法名称(如 "HKDF-SHA256")创建 KDF 实例。 -
KDF.deriveKey(String algorithm, AlgorithmParameterSpec spec)
:为指定算法派生密钥。 -
KDF.deriveData(AlgorithmParameterSpec spec)
:派生字节数组数据(如熵、密钥材料)。
实战示例
java
// 创建 HKDF 实例
KeyDerivationFunction kdf = KeyDerivationFunction.create("HKDF");
// 从原始密钥、盐值、上下文信息中派生新密钥
SecretKey derived = kdf.deriveKey(originalKey, salt, info);
9. JEP 470:加密对象的 PEM 编码(预览)
该特性提出了一套新的预览 API,用于处理"隐私增强邮件(PEM)"格式。开发者可通过它将密钥、证书等安全对象编码为 PEM 文本,也能将 PEM 文本解码为对应的 Java 对象。
其核心目标是提供简洁直观的 API,简化 PEM 编码数据与对应 Java 对象之间的转换,降低开发者的使用成本。
什么是 PEM?
PEM(Privacy-Enhanced Mail)是一种广泛使用的文本格式,用于编码证书、公钥、私钥等加密对象。PEM 文件本质是 Base64 编码的二进制数据,前后包裹着人类可读的头部与尾部标识,例如:
css
-----BEGIN CERTIFICATE-----
...Base64 编码的数据...
-----END CERTIFICATE-----
PEM 是 TLS/SSL 证书、SSH 密钥及众多安全工具的标准格式。
10. JEP 508:向量 API(第十次孵化)
Java 25 中的向量 API(JEP 508,第十次孵化)是一套持续演进的高性能 API,它允许开发者编写利用 SIMD(单指令多数据)硬件能力的向量化代码------相比传统标量代码(非向量代码),能显著提升计算密集型场景的速度,且代码兼具可移植性与可读性。
该 API 自 JDK 16 起以孵化特性推出,在后续每个 JDK 版本(直至 JDK 24)中均有更新;如今在 JDK 25 中继续孵化,并带来以下关键改进:
-
API 增强 :
VectorShuffle
现在支持直接操作堆外内存(MemorySegment
)中的数据。 -
可维护性提升:内部实现改用标准的"外部函数与内存 API"调用数学库,替代了 JVM 中复杂的自定义代码,降低维护难度。
-
新增硬件支持:在兼容的 x64 CPU 上,自动对 Float16 类型的操作(如加法、乘法)进行向量指令优化。
该 API 会持续孵化,直至 Project Valhalla 的核心特性就绪------届时向量 API 将适配这些特性,进入预览阶段。
11. JEP 519:紧凑对象头
紧凑对象头最早在 JDK 24 中作为"标准对象头布局的实验性替代方案"推出(大型特性通常会采用这种谨慎的发布策略,以便充分测试)。
JEP 519 在 Java 25 中将其升级为正式的生产级特性,能同时提升 Java 应用的内存利用率与运行性能------尤其对包含大量小对象的应用效果显著。该增强与"Project Lilliput"(旨在大幅降低 Java 内存占用)的目标高度一致。
核心优化:在 64 位 JVM 上,对象头大小从 128 位(16 字节)缩减至 64 位(8 字节),减少内存消耗的同时,还能提升缓存命中率,进而优化性能。
12. JEP 521:分代 Shenandoah 垃圾回收器
分代 Shenandoah 最早在 JDK 24 中以 JEP 404 作为实验性特性推出,如今在 JDK 25 中升级为正式特性,不再需要通过实验性 JVM 参数启用。
Shenandoah 本身是一款低停顿的并发垃圾回收器(GC),其设计目标是通过"与应用线程并发执行大部分 GC 工作",将传统的"Stop-The-World(STW)"停顿时间控制在 10ms 以内,非常适合大堆内存与延迟敏感型应用。
分代 Shenandoah 在原有基础上引入了"分代垃圾回收"支持,按照对象存活时间将堆内存划分为两个区域:
-
年轻代(Young Generation):存放新创建的对象,这类对象通常很快会变为不可达(即"短命对象")。
-
老年代(Old Generation):存放经历多次 GC 仍存活的对象(即"长命对象")。
这种设计遵循"弱分代假设"------大多数对象在创建后不久就会被回收,通过针对性地对不同代采用不同 GC 策略,能进一步降低停顿时间、提升内存管理效率。
13. JEP 514:提前编译命令行易用性优化
提前编译(AOT)允许在运行前将部分 Java 字节码编译为本地代码,通过减少运行时即时编译(JIT)的工作量,缩短应用启动时间。
Java 24 引入了"基于应用工作负载记录 AOT 缓存"的能力------后续运行时可通过预加载、链接类,实现更快启动;而 JEP 514 在 Java 25 中新增了 -XX:AOTCacheOutput
命令行选项,将"记录"与"生成缓存"两个阶段合并为一次 JVM 调用,具体流程如下:
-
运行应用,在内部将类加载行为记录到临时文件。
-
根据
-XX:AOTCacheOutput
指定的路径,生成 AOT 缓存文件。 -
自动清理临时文件,无需手动操作。
实战示例
bash
java -XX:AOTCacheOutput=app.aot -cp app.jar MainClass
该命令将原本需要两步完成的操作(记录类加载、生成缓存)简化为一条命令,大幅提升了 AOT 编译的易用性。
14. JEP 515:提前编译方法分析
Java 25 引入的"提前编译方法分析"特性,会在应用的"训练运行"阶段收集方法执行概况,并将这些数据存储到 AOT 缓存中。收集的信息包括方法执行频率、典型对象类型及其他运行时行为------这些数据能帮助 JVM 的 JIT 编译器在启动时更快、更精准地完成热点识别与代码优化。
简单来说,它允许 JVM 在启动时加载"从之前训练运行中获取的执行概况",跳过初始的热点探测阶段,直接进入优化流程,从而缩短 JIT 优化的耗时。
15. JEP 503:移除 32 位 x86 端口
JEP 503 移除了 OpenJDK 中 HotSpot JVM 针对 32 位 x86(Intel/AMD)架构的源代码与构建支持。该端口在 JDK 24(JEP 501)中已被标记为废弃,如今在 JDK 25 中完全移除。
移除的内容
-
32 位 x86 架构特有的源代码文件。
-
用于编译 32 位 x86 版本的构建脚本与配置。
-
仅针对 32 位 x86 运行的测试用例。
-
该平台特有的兼容性代码与降级逻辑。
仍支持的平台
-
其他 32 位平台(如 ARM32)仍正常支持。
-
64 位 x86(x86-64 / AMD64)仍是 Intel/AMD 硬件上的主要支持架构。
-
零端口(Zero port,一种与架构无关的解释器模式)仍可用,但不支持 JIT 优化。
对开发者的影响
-
运行在 64 位系统上的应用无任何变化。
-
仍在使用 32 位 x86 JVM 的legacy用户,需考虑迁移至 64 位 JVM。
-
不支持的硬件上可使用零解释器,但性能会有所下降。
16. JEP 509:JFR CPU 时间分析
Java 飞行记录器(JFR)是 JVM 内置的性能分析与诊断工具。传统上,JFR 通过"按实际流逝时间定期采样调用栈"的方式估算 CPU 使用率,这种方式精度有限。
JEP 509 在 Linux 平台上引入了"实验性 CPU 时间分析"特性------不再按流逝时间采样,而是根据线程消耗的 CPU 时间捕获调用栈,大幅提升了 CPU 使用率分析的准确性。
17. JEP 518:JFR 协作式采样
传统 JFR 采用"异步采样"方式:通过在任意点暂停线程来收集调用栈,这种方式依赖启发式算法在"非安全状态"下遍历线程栈,稳定性与可扩展性存在局限。
JEP 518 重新设计了 JFR 采样机制,改为"在 JVM 安全点(Safepoint)进行协作式采样"------线程仅在安全状态下被采样,避免了非安全状态下的栈遍历风险,显著提升了采样的稳定性与可扩展性。
18. JEP 520:JFR 方法计时与追踪
JEP 520 为 JFR 新增了"方法级计时与追踪"能力------通过字节码插桩,精准记录指定 Java 方法的执行时间与调用轨迹。相比传统采样,它能提供更精确的方法级统计数据,包括调用次数、执行时长等。
该特性大幅增强了 JFR 对方法级性能分析的支持,开发者无需大幅修改应用代码,就能获得详细的方法执行信息,便于性能调优与问题排查,同时将性能开销控制在合理范围。
Java 25 新特性 JEP 总表
| JEP 编号 | 特性名称 | 状态 | 分类 |
|----------|-----------------------------------|--------------|--------------|
| 470 | 加密对象的 PEM 编码 | 预览 | 安全 |
| 502 | 稳定值 | 预览 | 核心库 |
| 503 | 移除 32 位 x86 端口 | 正式 | 移除特性 |
| 505 | 结构化并发 | 第五次预览 | 并发 |
| 506 | 作用域值 | 正式 | 并发 |
| 507 | 模式匹配、instanceof 与 switch 支持基本类型 | 第三次预览 | 语法 |
| 508 | 向量 API | 孵化 | 性能 |
| 509 | JFR CPU 时间分析 | 实验性 | 性能分析 |
| 510 | 密钥派生函数 API | 正式 | 安全 |
| 511 | 模块导入声明 | 正式 | 语法 |
| 512 | 紧凑源文件与实例 main 方法 | 正式 | 语法 |
| 513 | 灵活的构造函数体 | 正式 | 语法 |
| 514 | 提前编译命令行易用性优化 | 正式 | 性能 |
| 515 | 提前编译方法分析 | 正式 | 性能 |
| 518 | JFR 协作式采样 | 正式 | 性能分析 |
| 519 | 紧凑对象头 | 正式 | 性能 |
| 520 | JFR 方法计时与追踪 | 正式 | 性能分析 |
| 521 | 分代 Shenandoah 垃圾回收器 | 正式 | 垃圾回收 |
总结
Java 25 的更新让 Java 无论是对初学者还是专业开发者都更友好:紧凑源文件等特性降低了新手的入门门槛,而并发、性能、安全领域的增强则帮助资深开发者构建更健壮、可扩展的应用。若需深入了解某特性的技术细节,可参考其对应的 JEP 文档。
Java 的演进从未停歇,这也让它始终保持着编程语言领域的核心地位,持续满足现代应用开发的需求。