写一篇全是AI翻译味的文章吧

如何通过IDEA帮助开发者更快的与JDK25达成一致

IDEA EAP 2025.3 率先支持了JDK25,我们可以通过IDEA预热25的使用

image-20250919151212429

安装

我们可以通过JetBrains的官方工具Toolbox进行全家桶的安装,在这里我们要 选择预览版2025.3,该版本针对JDK25进行了支持

image-20250919104128439

image-20250919104207415

image-20250919104439641

新建一个Java项目,java版本选择25,我们可以在JDK选择页面直接下载所需的JDK版本,这里我们选择OpenJDK 25

image-20250919104904998

image-20250919104904998

介绍

初见端倪,我们已经看到了两个新特性了

image-20250919113003649

紧凑源文件和实例主方法JEP 512

在紧凑型源文件中,你可以不使用public class Main

在紧凑型主方法中可以使用void main()来替代public static void main(String[] args)

当你需要使用args时,你只需要在IDEA下面的代码快中直接写下args

image-20250919135039852

image-20250919135230397

IDEA会自动补全我们需要的参数

通过alt+enter,我们可以快速的将紧凑型和标准型相会转换

image-20250919153521271

我们也可以直接使用IDEA来创建一个紧凑型类

image-20250919161438882

注意,紧凑型类必须包含一个紧凑型主方法,也就是说,除了主函数,其他的类还是之前的样子

image-20250919161400933

ClassIO类

该类提供了几个常用的输入输出的功能,可以不使用Scanner进行输入

image-20250919151549121

模块导入声明 JEP511

在JDK9的时候引入了模块化系统,在那之前,人们通过package来进行结构管理

先介绍下module-info.java,它可以介绍一个module依赖的包,也可以将自己的包释放出去

其他的包在进行开发的时候在自己的module-info.java中声明自己的依赖,就相当于直接引入了这些类,而不用再次import

在JDK25之后,JAVA允许开发者直接在java文件中使用module,类似于import module java.base;

柔性构造体 JEP 513

在JDK22之前,在构造函数调用超类的构造函数必须作为首行,此功能在22预览

image-20250919160820656

在25之后你可以调用超类之前写自己的代码

image-20250919161807869

Scoped Values JEP 506

在之前,我们使用ThredLocal来进行不使用方法函数来传值,现在,我们拥有更好的方式来进行这个操作,使用Scoped Values

ScopedValue<Integer> JAVA_VERSION = ScopedValue.newInstance();
void main() {
ScopedValue.where(JAVA_VERSION, 24).run(this::update);
}
private void update() {
ScopedValue.where(JAVA_VERSION, JAVA_VERSION.get() + 1).run(() -> {
IO.println("Hello, Java " + JAVA_VERSION.get()); // prints 25
});
}

运行Main函数之后,我们得到输出 Hello, Java 25

那我们如果之际不定义ScopedValue的值,直接get会获得什么呢

ScopedValue<Integer> JAVA_VERSION = ScopedValue.newInstance();
void main() {
ScopedValue.where(JAVA_VERSION, 24).run(this::update);
Integer i = JAVA_VERSION.get();
IO.println("JAVA_VERSION:" + i);
}
private void update() {
ScopedValue.where(JAVA_VERSION, JAVA_VERSION.get() + 1).run(() -> {
IO.println("Hello, Java " + JAVA_VERSION.get()); // prints 25
});
}
Hello, Java 25
Exception in thread "main" java.util.NoSuchElementException: ScopedValue not bound
at java.base/java.lang.ScopedValue.slowGet(ScopedValue.java:571)
at java.base/java.lang.ScopedValue.get(ScopedValue.java:564)
at Main.main(Main.java:5)

Hello, Java 25是我们的update函数里面输出的,main函数里的print得到了一个NoSuchElementException,因为事先没有对JAVA_VERSION进行绑定,也就是只有在ScopedValue where里面的run才能拿到定义的值。

性能和探查器改进

当前,除了Oracle喂给我们的这些语法糖,我们还获得了JDK25的性能提升。

AOT方法分析 JEP 515

什么是AOT(Ahead-of-Time),其实预编译在Java9中就已经被提出,它的作用是直接将字节码编译为机器码,直接运行机器码效率更高,但是编译时间会很长。正常使用的是JIT(Just-In-Time),JVM在运行应用程序的时候动态解释运行。

这次JDK25提出的是AOT方法分析是基于JDK24的JEP 483:提前类加载和链接的延伸。JDK通过对应用程序运行时的信息生成缓存文件,下次运行的时候套用缓存就可以更快的启动,当然这些需要我们手动来操作,具体见JEP 483:提前类加载和链接

JEP 518:JFR 合作采样 + JEP 520:JFR 方法时序和跟踪

什么是JFR( JDK Flight Recorder),OpenJDK从11版本开始支持。它是一个低开销的数据收集框架,可用于在生产环境中分析Java应用和JVM运行状况及性能问题。

协作采样允许线程在安全点报告分析数据,以减少开销并提高准确性,方法计时和跟踪提供更精确、更详细的调用持续时间和调用堆栈信息。

JEP 519:紧凑对象标头

519将JEP 450的实验性更改为产品性,将 64 位 JVM 上的对象头从其较大的实验形式缩小为紧凑的 64 位布局。这减少了内存开销(尤其是对于许多小对象)并改进了垃圾回收 (GC) 和缓存行为。

img

img

Shenandoah 情人渡 分代垃圾回收器JEP 521

情人渡

  1. 初始标记(Init Mark)[STW] [同G1]

    标记与GC Roots直接关联的对象。

  2. 并发标记(Concurrent Marking)[同G1]

    遍历对象图,标记全部可达对象。

  3. 最终标记(Final Mark)[STW] [同G1]

    处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集。使用STAB(Snapshot-At-The-Beginning)机制实现。

  4. 并发清理(Concurrent Cleanup)

    回收所有不包含任何存活对象的Region(这类Region被称为Immediate Garbage Region)。

  5. 并发回收(Concurrent Evacuation)

    将回收集里面的存活对象复制到一个其他未被使用的Region中。

    并发复制存活对象,就会在同一时间内,同一对象在堆中存在两份,那么就存在该对象的读写一致性问题。Shenandoah通过使用转发指针将旧对象的请求指向新对象解决了这个问题。这也是Shenandoah和其他GC最大的不同。

  6. 初始引用更新(Init Update References)[STW]

    并发回收后,需要将所有指向旧对象的引用修正到新对象上。这个阶段实际上并没有实际操作,只是设置一个阻塞点来保证上述并发操作均已完成。

  7. 并发引用更新(Concurrent Update References)

    顺着内存物理地址线性遍历堆空间,更新并发回收阶段复制的对象的引用。

  8. 最终引用更新(Final Update References)[STW]

    堆空间中的引用更新完毕后,最后需要修正GC Roots中的引用。

  9. 并发清理(Concurrent Cleanup)

    此时回收集中Region应该全部变成Immediate Garbage Region了,再次执行并发清理,将这些Region全部回收。

STAB算法如下

白色是未标记;灰色自身被标记,引用的对象未标记;黑色自身与引用对象都已标记。

快照结束后,白色的作为垃圾被回收

img

BrooksPointers转发指针实现

为了在对象复制之后,访问老对象也可以找到新对象(而不是放到转转上),我们使用转发指针实现了继续访问。

所谓的转发指针就是在每个对象的对象头前面添加了一个新的字段,也就是对象头前面多了根指针。对于未移动的对象而言,指针指向的地址是自己,但对于移动的对象而言,该指针指向的为对象新地址中的BrooksPointers转发指针。

每次获取对象时都需要看下该对象的指针指向哪里,最终指向才是对象的位置。但是每次访问都需要多上至少一次额外转发。

读 + 写屏障

但是转发指针在修改时也存在并发问题。

  1. GC线程正在复制旧对象去到新的区域。
  2. 用户线程此时更新了原本对象的数据。
  3. GC线程将原本旧对象的转发指针指向新对象的转发指针。

分析如上情况可以得知,因为GC线程已经复制对象了,只是还没来得及更新旧对象的转发指针,所以导致了用户操作落到了旧对象上面,从而出现了安全问题。而ShenandoahGC中则采用读、写屏障确保了步骤1 3是原子性的,从而解决了该问题。

延展阅读

深入解析 Java 中的 module-info.java

GC - Java 垃圾回收器之G1详解

Shenandoah GC

G1、ZGC、ShenandoahGC高性能收集器深入剖析

现代JDK中亚毫秒级停顿的处理器:Shenandoah和ZGC