京东自营 + 国补 iPhone 历史最低价          国家补贴 享8折

Java 25 新特性

概述

Java 25 LTS 版本已按计划于2025年9月16日正式发布,该版本提供18项新特性(含6项预览功能),延续了两年发布一次LTS版本的策略。

正式特性

  • JEP 503:移除 32 位 x86 端口
  • JEP 506:作用域值由 JEP 429 (Java 20) 提议孵化,经过 Java21-24 四次预览,最终在JEP 506(Java 25)中正式引入。
    • JEP 446(Java 21):作用域值(预览)
    • JEP 464(Java 22):作用域值(第二次预览)
    • JEP 481(Java 23):作用域值(第三次预览)
    • JEP 487(Java 24):作用域值(第四次预览)
  • JEP 510:密钥派生函数 API
  • JEP 511:模块导入声明由 JEP 476(Java 23) 提议预览,经过 JEP 494(Java 24)再次预览,最终在 JEP 511(Java 25)中正式引入。
  • JEP 512:压缩源文件和实例主方法
    • JEP 445(Java 21): 未命名类和实例主方法(预览)
    • JEP 463(Java 22): 隐式声明的类和实例主方法(第二次预览)
    • JEP 477(Java 23): 隐式声明的类和实例主方法(第三次预览)
    • JEP 495(Java 24):简单源文件和实例主方法(第四次预览)
  • JEP 513:灵活的构造函数体经过Java22-24三次预览,最终在JEP 513(Java 25)中正式引入。
    • JEP 447(Java 22): super() 前语句(预览)
    • JEP 482(Java 23): 灵活的构造函数(第二次预览)
    • JEP 492(Java 24):灵活的构造函数体(第三次预览)
  • JEP 514:超前命令行
  • JEP 515:超前方法剖析
  • JEP 518:JFR 合作采样
  • JEP 519:紧凑对象标头
    • JEP 450(Java 24):紧凑对象标头(实验性)
  • JEP 520:JFR 方法时序和跟踪
  • JEP 521:分代 Shenandoah
    • JEP 404(Java 24):分代 Shenandoah(实验性)

预览特性

  • JEP 470:加密对象的 PEM 编码(预览)
  • JEP 502:稳定值(预览)
  • JEP 505:结构化并发(第五次预览)
  • JEP 507:模式、instanceof 和 switch中的原始类型(第三次预览)
  • JEP 508:向量 API(第十次孵化)
  • JEP 509:JFR CPU 时间分析(实验性)

JEP 470:加密对象的 PEM 编码(预览)

::: info JEP 470: PEM Encodings of Cryptographic Objects (Preview)
Introduce an API for encoding objects that represent cryptographic keys, certificates, and certificate revocation lists into the widely-used Privacy-Enhanced Mail (PEM) transport format, and for decoding from that format back into objects. This is a preview API.
:::

引入一个 API,用于将表示加密密钥、证书和证书吊销列表的对象编码为广泛使用的隐私增强型邮件 (PEM) 传输格式,并从该格式解码回对象。这是一个预览 API。

JEP 502: 稳定值(预览)

::: info JEP 502: Stable Values (Preview)
Introduce an API for stable values, which are objects that hold immutable data. Stable values are treated as constants by the JVM, enabling the same performance optimizations that are enabled by declaring a field final. Compared to final fields, however, stable values offer greater flexibility as to the timing of their initialization. This is a preview API.
:::

为稳定值引入 API,稳定值是保存不可变数据的对象。稳定值被 JVM 视为常量,从而实现与声明字段final相同的性能优化。与final字段相比,稳定值在初始化时间方面提供了更大的灵活性。这是一个预览 API。

稳定值具有以下特性:

  • 灵活初始化:稳定值为我们提供了与final字段相同的初始化保证,同时保留了可变非final字段的灵活性。
  • 支持稳定列表:稳定值 API 还引入了对稳定列表的支持,这是一种特殊的稳定值,用于存储不可变的元素列表。

核心目标

  • 将稳定值的创建与其初始化分离,而不会造成重大性能损失。
  • 保证稳定值最多初始化一次,即使在多线程程序中也是如此。

现有问题

final 字段

class OrderController {

    private final Logger logger = Logger.create(OrderController.class);

    void submitOrder(User user, List<Product> products) {
        logger.info("order started");
        ...
        logger.info("order submitted");
    }

}

由于 logger 是 OrderController 类的 final 字段,因此在创建 OrderController 实例时,必须立刻初始化该字段。这意味着创建新的 OrderController 可能会很慢,毕竟,获取日志记录器有时需要进行昂贵的操作,如读取和解析配置数据,或准备记录日志事件的存储空间。这种初始化工作不仅不利于应用程序的启动,而且可能没有必要。毕竟,有些组件可能永远都不需要记录事件,那么为什么要在前面做这些昂贵的工作呢?

延迟初始化

由于这些原因,我们通常会尽可能推迟复杂对象的初始化时间,以便只在需要时才创建这些对象。我们可以将其初始化移到 getter 方法中。该方法会检查是否已经存在日志对象;如果不存在,则会创建一个新的日志对象,并将其存储在一个 logger 字段中。虽然这种方法可以改善应用程序的启动,但也有一些缺点:

  • 直接访问 logger 字段可能会暴露一个尚未初始化的字段,从而导致 NullPointerException。
  • 多线程环境下,可能会导致创建多个日志记录器对象。虽然可以通过一些技术来解决这个问题,但都引入了复杂性,并且存在一些缺点或限制:
    • Class-holder允许常量折叠优化,但它只适用于 static 字段,且每个字段都需要一个单独的持有者类。
    • 双重检查锁无法应用常量折叠优化,而且必须声明 logger 字段为 volatile。

总结

我们可能会期望 JVM 通过对已初始化的 logger 字段进行常量折叠访问,或在 getLogger 方法中省略 logger == null 检查等方式来优化对 logger 字段的访问。遗憾的是,由于字段不再是 final,JVM 无法相信其内容在初始更新后永远不会改变。使用可变字段实现的灵活初始化并不高效。

简而言之,Java 语言允许我们控制字段初始化的方式要么太受约束,要么太不受约束。一方面,final字段受到的约束太强,需要在对象或类的生命周期早期进行初始化,这通常会降低应用程序的启动性能。另一方面,通过使用非final可变字段进行灵活初始化使得推理正确性变得更加困难。不变性和灵活性之间的紧张关系导致开发人员采用不完美的技术,这些技术无法解决根本问题,并导致代码更加脆弱且难以维护。

稳定值

稳定值是一个对象,其类型为 StableValue,只保存一个数据值,即其内容。稳定值必须在首次检索其内容之前的一段时间初始化,此后将不可更改。稳定值是实现延迟不变性的一种方法。

稳定对象

有两种指定初始化的方式:

  • 使用StableValue::orElseSet()方法在 getter 方法中指定初始化
  • 使用StableValue.supplier()方法在声明位置指定初始化
class OrderController {
    // 在 getter 方法中指定初始化
    private final StableValue<Logger> logger = StableValue.of();

    Logger getLogger() {
        // orElseSet方法保证提供的 lambda 表达式仅计算一次,即使logger.orElseSet(...)同时调用也是如此。
        return logger.orElseSet(() -> Logger.create(OrderController.class));
    }

    // 在声明位置指定初始化
    private final Supplier<Logger> logger
        = StableValue.supplier(() -> Logger.create(OrderController.class));

    void submitOrder(User user, List<Product> products) {
        logger.get().info("order started");
        ...
        logger.get().info("order submitted");
    }

}

稳定列表

class Application {
    static final List<OrderController> ORDERS
        = StableValue.list(POOL_SIZE, _ -> new OrderController());
    public static OrderController orders() {
        long index = Thread.currentThread().threadId() % POOL_SIZE;
        return ORDERS.get((int)index);
    }
}

JEP 503:移除 32 位 x86 端口

::: info JEP 503: Remove the 32-bit x86 Port
Remove the source code and build support for the 32-bit x86 port. This port was deprecated for removal in JDK 24 (JEP 501) with the express intent to remove it in a future release.
:::

移除32位x86端口的源代码和构建支持。该端口在JDK 24(JEP 501)中已被标记为弃用,明确计划在未来版本中移除。

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

::: info JEP 505: Structured Concurrency (Fifth Preview)
Simplify concurrent programming by introducing an API for structured concurrency. Structured concurrency treats groups of related tasks running in different threads as single units of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability. This is a preview API.
:::

<br />

通过引入结构化并发 API 来简化并发编程。结构化并发将不同线程中运行的相关任务组视为单个工作单元,从而简化错误处理和取消,提高可靠性并增强可观测性。这是一个预览 API。

<br />

结构化并发

JEP 506:作用域值

::: info JEP 506: Scoped Values
Introduce scoped values, which enable a method to share immutable data both with its callees within a thread, and with child threads. Scoped values are easier to reason about than thread-local variables. They also have lower space and time costs, especially when used together with virtual threads (JEP 444) and structured concurrency (JEP 505).
:::

引入作用域值,使方法能够与线程中的被调用方以及子线程共享不可变数据。作用域值比线程局部变量更容易推理。它们还具有较低的空间和时间成本,特别是当与虚拟线程(JEP 444)和结构化并发(JEP 505)一起使用时。

有一个小的更改:ScopedValue.orElse 方法不再接受 null 作为其参数。

作用域值

JEP 507:模式、instanceof 和 switch中的原始类型(第三次预览)

::: info JEP 507: Primitive Types in Patterns, instanceof, and switch (Third Preview)
Enhance pattern matching by allowing primitive types in all pattern contexts, and extend instanceof and switch to work with all primitive types. This is a preview language feature.
:::

通过允许在所有模式上下文中使用原始类型来增强模式匹配,并扩展 instanceof 和 switch 以使用所有原始类型。这是一个预览语言功能。

模式、instanceof 和 switch中的原始类型

JEP 508:向量 API(第十次孵化)

::: info JEP 508: Vector API (Tenth Incubator)
Introduce an API to express vector computations that reliably compile at runtime to optimal vector instructions on supported CPUs, thus achieving performance superior to equivalent scalar computations.
:::

<br />

引入一个 API 来表达矢量计算,这些计算在运行时可靠地编译为受支持的 CPU 上的最佳矢量指令,从而实现优于等效标量计算的性能。

JEP 509: JEP 509:JFR CPU 时间分析(实验性)

::: info JEP 509: JFR CPU-Time Profiling (Experimental)
Enhance the JDK Flight Recorder (JFR) to capture more accurate CPU-time profiling information on Linux. This is an experimental feature.
:::

增强 JDK 飞行记录器 (JFR) 以在 Linux 上捕获更准确的 CPU 时间分析信息。这是一个实验性功能。

JEP 510:密钥派生函数 API

::: info JEP 510: Key Derivation Function API
Introduce an API for Key Derivation Functions (KDFs), which are cryptographic algorithms for deriving additional keys from a secret key and other data.
:::

引入密钥派生函数(KDF)的 API,这是一种用于从密钥和其他数据派生其他密钥的加密算法。

JEP 511:模块导入声明

::: info JEP 511: Module Import Declarations
Enhance the Java programming language with the ability to succinctly import all of the packages exported by a module. This simplifies the reuse of modular libraries, but does not require the importing code to be in a module itself.
:::

通过简洁地导入模块导出的所有包的功能来增强 Java 编程语言。这简化了模块库的重用,但不要求导入代码本身必须在模块中。

JEP 512:压缩源文件和实例主方法

::: info JEP 512: Compact Source Files and Instance Main Methods
Evolve the Java programming language so that beginners can write their first programs without needing to understand language features designed for large programs. Far from using a separate dialect of the language, beginners can write streamlined declarations for single-class programs and then seamlessly expand their programs to use more advanced features as their skills grow. Experienced developers can likewise enjoy writing small programs succinctly, without the need for constructs intended for programming in the large.
:::

发展 Java 编程语言,使初学者无需了解专为大型程序设计的语言功能,即可编写自己的第一个程序。初学者无需使用某种独立的语言变体,即可为单类程序编写简化的声明,然后随着技能的增长无缝扩展他们的程序以使用更高级的功能。经验丰富的开发者同样可以简洁地编写小型程序,而不必使用那些为大规模编程设计的结构。

简单说就是省略显式类声明和 public static 修饰符,简化输出语句,来简化入门代码和脚本开发。

void main() {
    println("Hello Java 23!"); // 隐式调用 System.out.println
}

改动点:

  • 将简单的源文件重命名为紧凑的源文件
  • 用于基本控制台 I/O 的新 IO 类现在位于 java.lang 包中,被每个源文件隐式导入。
  • IO 类的静态方法不再隐式导入到紧凑的源文件中。因此,这些方法的调用必须命名类,例如IO.println("Hello, world!"), 除非显式导入这些方法。
  • IO 类的实现现在基于System.outSystem.in, 而不是java.io.Console 类。

JEP 513:灵活的构造函数体

::: info JEP 513: Flexible Constructor Bodies
In the body of a constructor, allow statements to appear before an explicit constructor invocation, i.e., super(...) or this(...). Such statements cannot reference the object under construction, but they can initialize its fields and perform other safe computations. This change allows many constructors to be expressed more naturally. It also allows fields to be initialized before they become visible to other code in the class, such as methods called from a superclass constructor, thereby improving safety.
:::

在构造函数体内,允许在显式构造函数调用(即 super(...) 或 this(...))之前出现语句。这些语句不能引用正在构造的对象,但可以初始化其字段并执行其他安全计算。这一变化使许多构造函数能够更自然地表达。它还允许在字段对类中其他代码(如从超类构造函数调用的方法)可见之前对其进行初始化,从而提高安全性。

JEP 514:超前命令行

::: info JEP 514: Ahead-of-Time Command-Line Ergonomics
Make it easier to create ahead-of-time caches, which accelerate the startup of Java applications, by simplifying the commands required for common use cases.
:::

这项改进旨在简化创建 Java AOT(提前编译)缓存的命令,让普通开发者更容易使用这项高级技术,从而大幅缩短 Java 应用的启动时间。

在之前的 Java 版本中,创建 AOT 缓存非常复杂:

java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=./app_cache.so -XX:SharedArchiveFile=./app_cached.jsa -Xshare:dump -cp myapp.jar com.example.MainClass

新版本简化了这个过程:

java -Xaot:force -jar myapp.jar
# 或者
java -Xaot:generate -jar myapp.jar

这项改进使得 Java 开发者能够通过简单的命令参数,就能获得类似原生编译语言的启动速度,同时保持 Java 的跨平台优势和运行时优化能力。这标志着 Java 在云原生时代的重要进化,让高性能 Java 应用的门槛大大降低。

JEP 515:超前方法剖析

::: info JEP 515: Ahead-of-Time Method Profiling
Improve warmup time by making method-execution profiles from a previous run of an application instantly available, when the HotSpot Java Virtual Machine starts. This will enable the JIT compiler to generate native code immediately upon application startup, rather than having to wait for profiles to be collected.
:::

当 HotSpot Java 虚拟机启动时,可立即从应用程序的上一次运行中获取方法执行配置文件,从而缩短预热时间。这将使 JIT 编译器能在应用程序启动时立即生成本地代码,而无需等待收集配置文件。

<br />

要解决的问题:Java 启动后的“热身”阶段过慢

HotSpot JVM 刚启动时,所有字节码都运行在解释模式下,速度很慢。为了让热点方法(频繁调用的方法)达到最佳性能,JVM 必须经历以下阶段:

  1. 解释执行 → 收集调用次数、分支频率等运行时剖析数据(profiling data)。
  2. JIT 编译器 → 根据剖析数据生成高度优化的本地机器码。
  3. 替换执行 → 把解释执行切换到本地机器码,性能飙升。
    这段“先解释、再编译、再切换”的时间就是常说的 warm-up(热身)时间 。 在大型应用里,warm-up 可能持续 数秒到数分钟 ,对云原生、Serverless、短生命周期任务极不友好。

JEP 515 如何工作:直接复用上一次运行的剖析数据

JEP 515 的思路可以概括为一句话:
“把上一次运行得到的剖析数据持久化到磁盘,下一次启动时直接加载。”

如何启用

java -XX:+UseAOTMethodProfiles -XX:AOTProfilePath=./myapp.prof -jar myapp.jar

总结

这项改进的本质是:将"学习成本"从运行时提前到构建时或第一次运行时,通过复用之前运行的经验来避免重复的学习过程。
就像一位经验丰富的司机:
传统方式:每次开车都要重新熟悉路况
新方式:使用之前的导航记录,一上路就知道最优路线

JEP 518:JFR 协作式采样

::: info JEP 518: JFR Cooperative Sampling
Improve the stability of the JDK Flight Recorder (JFR) when it asynchronously samples Java thread stacks. Achieve this by walking call stacks only at safepoints, while minimizing safepoint bias.
:::

提高 JDK Flight Recorder (JFR) 异步采样 Java 线程堆栈时的稳定性。为此,我们只在安全点处走查调用堆栈,同时尽量减少安全点偏差。

问题:异步采样为何不稳定?

  • 异步采样 指 JFR 在 不停止业务线程 的情况下,随时抓取线程的调用栈。
  • 旧实现直接在任意时刻“爬栈”,会遇到两类崩溃:
  1. 线程正好在执行 JIT 刚编译完但尚未完全同步的代码 ,栈帧结构不完整 → 爬栈越界。
  2. 线程正在 去优化(deoptimization) 的临界区 → 栈信息不一致。 结果:JVM 偶尔 crash,或者采样数据出现“幽灵栈帧”。

旧方案的权衡:要么“安全”要么“失真”

方案做法缺点
Stop-the-world让所有线程进入 safepoint 后再采样业务停顿(几十毫秒),破坏低延迟场景
冒险异步直接爬栈,不做全局停顿不稳定,可能 JVM 崩溃

新方案:只在 safepoint 采样,但最小化 safepoint 偏差

  • 核心思想
    • 把“采样请求”投递给目标线程,线程自愿在下一个 safepoint 完成采样;采样动作仍由业务线程自己执行,但必须在 safepoint 内完成,保证栈结构一致。
  • 关键机制
    • 不强制全局停顿:仅对“被采样线程”做一次轻量级 handshake,其他线程继续跑。
    • 时间窗口限制:若线程长时间未进入 safepoint(例如死循环内无 safepoint poll),采样请求超时放弃,避免饥饿。
    • 保存当前 rbp/rsp 等寄存器 → 构建可信栈帧。
    • 把栈信息写入 JFR 缓冲区。
    • JFR 给每个 Java 线程置位一个_should_sample_at_safepoint标志。
    • 协作标志
    • safepoint 轮询:线程在 safepoint 检查点发现该标志为真,立即:
    • 偏差控制
  • 实现细节
    • Handshakes(JDK 15+ 引入):单线程级 safepoint,不波及整个 JVM。
    • 偏向采样:默认只采样“运行中”线程,sleep/block 的线程直接跳过,减少无效 safepoint。
    • 自适应阈值:根据历史采样成功率动态调整超时时间,防止过度放弃或过度停顿。

一句话总结

JEP 518 把 “安全”与“低延迟” 从对立变为兼得 —— 线程自己走进安全点采样,既保证栈可信,又把停顿缩小到单线程级。

JEP 519:紧凑对象头

::: info JEP 519: Compact Object Headers
Change compact object headers from an experimental feature to a product feature.
:::

将紧凑对象头从实验功能改为产品功能。

核心原理

Java 对象在堆中的存储包含对象头实例数据。对象头又分为:

  • 标记字 (Mark Word):存储哈希码、GC 年龄、锁状态、线程持有的锁、偏向线程ID、偏向时间戳等运行时数据。
  • 类指针 (Class Pointer):指向对象的类元数据。
  • 数组长度:数组对象的头部还会额外包含该字段。

在 64 位 JVM 中,普通对象头通常占 12字节(96位) 至 16字节(128位)。对于许多平均大小仅为 32-64 字节的小对象来说,对象头的开销占比非常高,可能达到 20% 到 50%。JEP 450 通过以下方式将对象头压缩至 64 位 (8 字节)

  • 压缩类指针:将原本 32 位的压缩类指针进一步优化为 22 位,并将其编码嵌入标记字中。
  • 标记字的功能重组与升级:对固定的 64 位标记字空间进行了彻底的重新规划,将其划分为一个精细的功能位域集合,以同时承载原有和新增的信息
  • 锁机制革新:传统的偏向锁轻量级锁 会覆盖整个标记字(除锁标记位外),与紧凑对象头中必须保留类指针的设计冲突。因此,紧凑对象头不再支持偏向锁和栈锁。它依赖 JDK 22 中引入的对象监视表 来管理重量级锁信息。

新旧对象头布局对比

为了更直观地理解其变化,可以参考下表对传统对象头与紧凑对象头进行的对比:

Java传统对象头

传统对象头大小

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java