2025年10月10日星期五

Java 25 新特性详解与实战示例

Java 25 已于 2025 年 9 月 16 日正式发布,作为一款长期支持(LTS)版本,它在核心 Jav

Java 25 已于 2025 年 9 月 16 日正式发布,作为一款长期支持(LTS)版本,它在核心 Java 库、语言规范、安全性及性能等领域带来了大量增强。甲骨文(Oracle)计划为 Java 25 提供至少 8 年的支持服务,这意味着企业可以灵活安排迁移节奏,同时享受到最新特性带来的优势——包括更强大的 AI 能力与更高的开发者效率。

作为最新的 LTS 版本,Java 25 在语言语法、API、安全、性能与监控等方面均实现了重大升级,让 Java 对各类使用者而言都更易用、更强大。所有新特性均通过 JDK 增强提案(JEP)引入,因此下文将针对每个特性,依次介绍其对应的 JEP、核心升级内容,并提供实用的代码示例或应用场景。

目录

  1. JEP 507:模式匹配、instanceof 与 switch 支持基本类型(第三次预览)
  2. JEP 512:紧凑源文件与实例 main 方法
  3. JEP 513:灵活的构造函数体
  4. JEP 511:模块导入声明
  5. JEP 505:结构化并发(第五次预览)
  6. JEP 506:作用域值(Scoped Values)
  7. JEP 502:稳定值(Stable Values,预览)
  8. JEP 510:密钥派生函数 API
  9. JEP 470:加密对象的 PEM 编码(预览)
  10. JEP 508:向量 API(第十次孵化)
  11. JEP 519:紧凑对象头
  12. JEP 521:分代 Shenandoah 垃圾回收器
  13. JEP 514:提前编译命令行易用性优化
  14. JEP 515:提前编译方法分析
  15. JEP 503:移除 32 位 x86 端口
  16. JEP 509:JFR CPU 时间分析
  17. JEP 518:JFR 协作式采样
  18. JEP 520:JFR 方法计时与追踪

1. JEP 507:模式匹配、instanceof 与 switch 支持基本类型(第三次预览)

该特性的目标是实现"统一数据探索"——允许类型模式支持所有类型(无论是基本类型还是引用类型)。它最初由 JEP 455(JDK 23)提出,后经 JEP 488(JDK 24)第二次预览且未做修改,在 JDK 25 中以预览特性形式第三次推出,内容仍保持不变。

如今 Java 的模式匹配已支持基本类型,这不仅简化了代码,还减少了错误。开发者可直接对基本类型进行类型安全的匹配与解构,无需多余的装箱操作或冗长代码;针对基本类型的模式分支,也让 switch 和 instanceof 的用法更简洁,能更直观地实现基本类型的模式匹配。

实战示例

示例 1:switch 匹配基本类型

Object obj = 42;
switch (obj) {
caseint i -> System.out.println("基本类型 int:" + i);
casedouble d -> System.out.println("基本类型 double:" + d);
default -> System.out.println("其他类型");
}

示例 2:带条件判断的 switch 基本类型匹配

Integer code = -1;
switch (code) {
caseint n when n > 0 -> System.out.println("正整数:" + n);
caseint n when n < 0 -> System.out.println("负整数:" + n);
default -> System.out.println("零");
}

上述代码避免了不必要的强制转换与装箱/拆箱操作,逻辑更清晰。

示例 3:instanceof 匹配基本类型

Object obj = 10;
if (obj instanceofint val) {
    System.out.println("基本类型 int 值:" + val);
elseif (obj instanceofdouble 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,也不能访问未初始化的字段)。

核心能力

  1. 父类构造前的输入验证:提前过滤非法参数,避免将错误值传入父类。
  2. 父类构造参数的计算与预处理:可先处理参数,再传递给父类构造函数。
  3. 更简洁安全的对象构造:在执行昂贵的内存分配前快速失败,减少资源浪费。

限制条件

  • 调用构造函数链(super 或 this)前,不能读取 this 的字段或调用实例方法。
  • 字段可初始化,但禁止泄露"部分构造对象"的引用(避免其他代码访问未完全初始化的对象)。
  • 预构造阶段(super 调用前)允许使用大部分静态方法与本地工具方法。

实战示例

示例 1:防御式编程(参数验证)

publicclassPositiveBigIntegerextendsBigInteger{
publicPositiveBigInteger(long value){
// 先验证参数,再调用父类构造
if (value <= 0) {
thrownew IllegalArgumentException("值必须为正数");
        }
super(Long.toString(value));
    }
}

通过提前验证,避免将非法值传入父类构造函数。

示例 2:条件性调用父类构造

publicclassCustomLoggerextendsLogger{
publicCustomLogger(String config){
// 先根据配置计算日志级别,再传入父类
        String level = "INFO";
if ("debug".equals(config)) {
            level = "DEBUG";
        }
super(level);
    }
}

在调用父类构造前,可动态计算传入参数。

示例 3:调用工具方法预处理

publicclassMetricextendsDataPoint{
publicMetric(String key){
// 先调用静态工具方法标准化 key,再传入父类
        String normalizedKey = normalizeKey(key);
super(normalizedKey);
    }

privatestatic String normalizeKey(String key){
return key.trim().toLowerCase();
    }
}

预构造阶段可调用工具方法处理参数,提升代码复用性。

示例 4:记录(Record)构造函数与 JEP 513

public record ValidatedPoint(int x, int y){
// 非标准记录构造函数
publicValidatedPoint(int x, int y){
// 先验证参数,再调用默认构造(this(x, y))
        checkNonNegative(x, y);
this(x, y);
    }

privatestaticvoidcheckNonNegative(int x, int y){
if (x < 0 || y < 0) {
thrownew IllegalArgumentException("坐标不能为负数");
        }
    }
}

非标准的记录构造函数也能享受这种灵活性,提前完成参数校验。

示例 5:枚举(Enum)构造函数

publicenum Status {
    ACTIVE("A"), INACTIVE("I");

privatefinal String code;

    Status(String code) {
// 枚举构造中也能提前验证参数
if (!code.matches("[AI]")) {
thrownew IllegalArgumentException("状态码必须是 A 或 I");
        }
this.code = code;
    }
}

现在可在枚举字段初始化前完成参数验证,避免非法状态码。

4. JEP 511:模块导入声明

该特性最早在 Java SE 23 中预览,后在 Java SE 24 中第二次预览,如今在 Java 25 中正式定稿,且未做重大修改。

它为 Java 语言引入了"模块导入声明",允许开发者通过一条语句导入某个模块中所有导出包的公共类型,语法如下:

importmodule <模块名>;

通过 import module,开发者可一次性导入一个模块的所有导出类,该特性主要通过以下方式简化开发:

  1. 简化库的使用
    :一条语句导入模块所有类型,无需逐个导入包,降低模块化库的使用门槛。
  2. 减少导入冗余
    :用一条简洁的模块导入替代多条通配符包导入,减少代码头部的导入语句冗余。
  3. 降低学习成本
    :新手无需先了解类的具体包路径,就能直接使用 Java 核心类或第三方库。
  4. 保持兼容性
    :模块导入声明可与传统的包导入语句共存,不会产生冲突。
  5. 支持增量迁移
    :即使代码库尚未完全模块化,开发者也能通过模块导入享受便利。

关于该特性的详细示例与原理(如模块导入的工作机制、歧义处理、模块系统的传递依赖、java.base 模块的默认可用性、显式依赖 java.se 等),可参考 Java 24 中 JEP-494"模块导入声明(第二次预览)"的文档。

5. JEP 505:结构化并发(第五次预览)

什么是结构化并发?

结构化并发是一种并发编程范式,它将相关的并发任务视为一个"单一工作单元",并为其定义明确的动态作用域。Java 中实现该范式的核心 API 是 StructuredTaskScope,它能确保:

  • 子任务的生命周期被限制在代码块的词法作用域内(通常是 try-with-resources 语句)。
  • 错误处理、结果收集与取消操作均能以清晰的方式协同进行。
  • 中断的可观测性与传播能力得到增强。

这与"非结构化"线程管理(如 ExecutorServiceCompletableFuture)形成鲜明对比——在非结构化模式中,任务生命周期难以追踪,错误与取消操作可能无声传播或丢失。

为什么要用结构化并发?

  1. 简化错误处理
    :若任一子任务失败,其他子任务会自动取消,避免无效执行。
  2. 明确的生命周期
    :所有子任务在同一代码块内启动与结束,防止线程/资源泄漏。
  3. 协同取消
    :父作用域的中断会自动传播到所有子任务,确保取消操作的一致性。
  4. 清晰的结果组合
    :要么所有任务的结果都可用,要么触发整体失败,避免部分结果无效的情况。

结构化并发的演进历程

该特性的发展贯穿多个 JDK 版本:

  • JDK 19、20:以孵化特性推出。
  • JDK 21:进入预览阶段,fork 方法的返回类型从 Future 改为 Subtask
  • JDK 22、23、24:持续保持预览状态。
  • JDK 25:第五次预览,核心 API 优化——StructuredTaskScope 对象现在通过静态工厂方法创建:
    • 无参的 open() 工厂方法:适用于"等待所有子任务完成或任一子任务失败"的常见场景。
    • 接收 Joiner 参数的工厂方法:适用于复杂场景,允许开发者自定义结果处理策略。

实战示例

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(); // 等待所有子任务完成,若有异常则传播

// 所有子任务成功,组合结果返回
returnnew 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),但仅能在特定作用域内读写:

privatestaticfinal ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();

2. 为作用域绑定值

通过 ScopedValue.where() 方法绑定值,并在 lambda 中执行逻辑:

ScopedValue.where(USER_CONTEXT, currentUserContext)
    .run(() -> {
        processRequest(); // 作用域内的代码可访问 USER_CONTEXT
    });

processRequest() 方法及其调用栈中的所有代码(包括子线程),都能通过 USER_CONTEXT.get() 获取绑定的值;出了 run() 块后,USER_CONTEXT.get() 会抛出异常。

实战示例:Web 框架中的上下文传播

传统方式(使用 ThreadLocal)

privatestaticfinal ThreadLocal<FrameworkContext> CONTEXT = new ThreadLocal<>();

voidserve(Request request, Response response){
    CONTEXT.set(createContext(request));
try {
        Application.handle(request, response);
    } finally {
        CONTEXT.remove(); // 必须手动清理,否则内存泄漏
    }
}

风险:容易忘记清理导致内存泄漏,上下文生命周期模糊,线程复用可能引发数据污染。

作用域值方式

privatestaticfinal ScopedValue<FrameworkContext> CONTEXT = ScopedValue.newInstance();

voidserve(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 值必须非空。

抛出空指针异常的示例

import java.lang.ScopedValue;

publicclassScopedValueDemo{
privatestaticfinal ScopedValue<String> SCOPE = ScopedValue.newInstance();

publicstaticvoidmain(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 非空"规则。

正确示例

privatestaticfinal ScopedValue<String> SCOPED_USER = ScopedValue.newInstance();

publicstaticvoidmain(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 原子性地赋值(延迟初始化),确保仅一个线程能成功赋值。

实战示例

import java.lang.StableValue;

publicclassOrderController{
// 延迟初始化的不可变 Logger,线程安全且仅赋值一次
privatefinal StableValue<Logger> logger = StableValue.of();

public Logger getLogger(){
// 若未初始化,则通过 Supplier 安全创建,后续调用直接返回
return logger.orElseSet(() -> Logger.create(OrderController.class));
    }
}

第一次调用 getLogger() 时,会通过 Supplier 初始化 Logger;后续调用直接返回已创建的不可变实例。

与延迟初始化的对比

传统延迟初始化(无稳定值)

private Logger logger = null;

Logger getLogger(){
if (logger == null) {
        logger = Logger.create(OrderController.class);
    }
return logger;
}

问题:非线程安全(多线程下可能重复赋值),Logger 可被重新赋值,JVM 无法进行常量折叠优化。

稳定值方式(Java 25)

privatefinal StableValue<Logger> logger = StableValue.of();

Logger getLogger(){
return logger.orElseSet(() -> Logger.create(OrderController.class));
}

优势:线程安全(原子赋值)、仅赋值一次、JVM 初始化后将其视为常量优化。

进阶示例:启动性能优化

若应用中有多个重量级控制器,可通过稳定值延迟初始化,减少启动时的资源消耗:

publicclassApplication{
// 延迟初始化 OrderController 与 UserService
staticfinal StableValue<OrderController> orders = StableValue.of();
staticfinal StableValue<UserService> users = StableValue.of();

static OrderController getOrderController(){
// 仅在首次调用时创建 OrderController
return orders.orElseSet(OrderController::new);
    }
}

OrderController 和 UserService 仅在需要时才会被构造,避免启动时的不必要延迟;同时支持并发安全访问,且初始化后变为不可变对象。

特性对比表

特性
final 字段
Java 25 稳定值
立即初始化
必须
可选(支持延迟初始化)
引用不可变
是(赋值后不可变)
JVM 优化支持
是(赋值后支持)
线程安全
是(原子操作,最多赋值一次)
启动延迟
可能存在
无(延迟初始化)
预览状态
不适用
是(Java 25 中为预览)

优势与最佳实践

  1. 性能优化
    :通过延迟初始化减少应用启动时间,无需一次性构建所有对象。
  2. 线程安全
    StableValue 确保赋值操作"最多一次",即使多线程并发访问也安全。
  3. 代码可读性
    :API 明确标识"延迟初始化但不可变"的字段,逻辑更清晰。
  4. 兼容性
    :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)
    :派生字节数组数据(如熵、密钥材料)。

实战示例

// 创建 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 编码的二进制数据,前后包裹着人类可读的头部与尾部标识,例如:

-----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 中继续孵化,并带来以下关键改进:

  1. API 增强
    VectorShuffle 现在支持直接操作堆外内存(MemorySegment)中的数据。
  2. 可维护性提升
    :内部实现改用标准的"外部函数与内存 API"调用数学库,替代了 JVM 中复杂的自定义代码,降低维护难度。
  3. 新增硬件支持
    :在兼容的 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 调用,具体流程如下:

  1. 运行应用,在内部将类加载行为记录到临时文件。
  2. 根据 -XX:AOTCacheOutput 指定的路径,生成 AOT 缓存文件。
  3. 自动清理临时文件,无需手动操作。

实战示例

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 的演进从未停歇,这也让它始终保持着编程语言领域的核心地位,持续满足现代应用开发的需求。

没有评论:

发表评论

零成本白嫖 Experian 信用分|ITIN 一步查分教程

很多朋友问我:ITIN 能不能查 Experian 信用分?答案是:能!而且不用花钱。只要用一款叫 Nav 的 App,就能查到。 很多朋友问我:ITIN 能不能查 Experian 信用分? 答案是:能!而且不用花钱。 只要用一款叫 Nav 的 App,就能查到。 下面是一...