IDEA+JDK25
写一篇全是AI翻译味的文章吧
如何通过IDEA帮助开发者更快的与JDK25达成一致
IDEA EAP 2025.3 率先支持了JDK25,我们可以通过IDEA预热25的使用
安装
我们可以通过JetBrains的官方工具Toolbox进行全家桶的安装,在这里我们要 选择预览版2025.3,该版本针对JDK25进行了支持
新建一个Java项目,java版本选择25,我们可以在JDK选择页面直接下载所需的JDK版本,这里我们选择OpenJDK 25
介绍
初见端倪,我们已经看到了两个新特性了
紧凑源文件和实例主方法JEP 512
在紧凑型源文件中,你可以不使用public class Main
在紧凑型主方法中可以使用void main()来替代public static void main(String[] args)
当你需要使用args时,你只需要在IDEA下面的代码快中直接写下args
IDEA会自动补全我们需要的参数
通过alt+enter,我们可以快速的将紧凑型和标准型相会转换
我们也可以直接使用IDEA来创建一个紧凑型类
注意,紧凑型类必须包含一个紧凑型主方法,也就是说,除了主函数,其他的类还是之前的样子
ClassIO类
该类提供了几个常用的输入输出的功能,可以不使用Scanner进行输入
模块导入声明 JEP511
在JDK9的时候引入了模块化系统,在那之前,人们通过package来进行结构管理
先介绍下module-info.java
,它可以介绍一个module依赖的包,也可以将自己的包释放出去
其他的包在进行开发的时候在自己的module-info.java
中声明自己的依赖,就相当于直接引入了这些类,而不用再次import
在JDK25之后,JAVA允许开发者直接在java文件中使用module,类似于import module java.base;
柔性构造体 JEP 513
在JDK22之前,在构造函数调用超类的构造函数必须作为首行,此功能在22预览
在25之后你可以调用超类之前写自己的代码
Scoped Values JEP 506
在之前,我们使用ThredLocal来进行不使用方法函数来传值,现在,我们拥有更好的方式来进行这个操作,使用Scoped Values
ScopedValue<Integer> JAVA_VERSION = ScopedValue.newInstance(); |
运行Main函数之后,我们得到输出 Hello, Java 25
那我们如果之际不定义ScopedValue的值,直接get会获得什么呢
ScopedValue<Integer> JAVA_VERSION = ScopedValue.newInstance(); |
Hello, Java 25 |
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) 和缓存行为。
Shenandoah 情人渡 分代垃圾回收器JEP 521
-
初始标记(Init Mark)[STW] [同G1]
标记与GC Roots直接关联的对象。
-
并发标记(Concurrent Marking)[同G1]
遍历对象图,标记全部可达对象。
-
最终标记(Final Mark)[STW] [同G1]
处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集。使用STAB(Snapshot-At-The-Beginning)机制实现。
-
并发清理(Concurrent Cleanup)
回收所有不包含任何存活对象的Region(这类Region被称为
Immediate Garbage Region
)。 -
并发回收(Concurrent Evacuation)
将回收集里面的存活对象复制到一个其他未被使用的Region中。
并发复制存活对象,就会在同一时间内,同一对象在堆中存在两份,那么就存在该对象的读写一致性问题。Shenandoah通过使用转发指针将旧对象的请求指向新对象解决了这个问题。这也是Shenandoah和其他GC最大的不同。
-
初始引用更新(Init Update References)[STW]
并发回收后,需要将所有指向旧对象的引用修正到新对象上。这个阶段实际上并没有实际操作,只是设置一个阻塞点来保证上述并发操作均已完成。
-
并发引用更新(Concurrent Update References)
顺着内存物理地址线性遍历堆空间,更新并发回收阶段复制的对象的引用。
-
最终引用更新(Final Update References)[STW]
堆空间中的引用更新完毕后,最后需要修正GC Roots中的引用。
-
并发清理(Concurrent Cleanup)
此时回收集中Region应该全部变成
Immediate Garbage Region
了,再次执行并发清理,将这些Region全部回收。
STAB算法如下
白色是未标记;灰色自身被标记,引用的对象未标记;黑色自身与引用对象都已标记。
快照结束后,白色的作为垃圾被回收
BrooksPointers转发指针实现
为了在对象复制之后,访问老对象也可以找到新对象(而不是放到转转上),我们使用转发指针实现了继续访问。
所谓的转发指针就是在每个对象的对象头前面添加了一个新的字段,也就是对象头前面多了根指针。对于未移动的对象而言,指针指向的地址是自己,但对于移动的对象而言,该指针指向的为对象新地址中的BrooksPointers
转发指针。
每次获取对象时都需要看下该对象的指针指向哪里,最终指向才是对象的位置。但是每次访问都需要多上至少一次额外转发。
读 + 写屏障
但是转发指针在修改时也存在并发问题。
- GC线程正在复制旧对象去到新的区域。
- 用户线程此时更新了原本对象的数据。
- GC线程将原本旧对象的转发指针指向新对象的转发指针。
分析如上情况可以得知,因为GC线程已经复制对象了,只是还没来得及更新旧对象的转发指针,所以导致了用户操作落到了旧对象上面,从而出现了安全问题。而ShenandoahGC
中则采用读、写屏障确保了步骤1 3是原子性的,从而解决了该问题。