大水怪

一只专注技术的水怪

0%

JDK21升级记录

技术分享PPT,可以参考上面来做GC选型,特性选型,升级实践等

1.准备工作

idea 升级

Maven

maven下图的这个限制是在maven3.5.3加的,在idea 2023.6以后会影响依赖正常import

  • springboot2:3.16.0

  • springboot3:3.10.0-Java21

2.新特性一览

注意,预览功能非必要不建议使用!

JDK8-JDK21

https://openjdk.org/jeps/0

JEPs in JDK 17 integrated since JDK 11

JEPs in JDK 21 integrated since JDK 17

https://openjdk.org/projects/jdk9/

https://openjdk.org/projects/jdk/10/

https://openjdk.org/projects/jdk/11/

https://openjdk.org/projects/amber/

以下表格(去除了大部分日常开发不需要感知的特性)中按照个人理解根据重要性(影响程度,以及我们日常开发需要关注的程度)进行排序,会比较主观,欢迎补充和指正

这个仓库演示了jdk9-18的新特性 https://github.com/nipafx/demo-java-x

oracle给出的重大变更指南 https://docs.oracle.com/en/java/javase/21/migrate/significant-changes-jdk-release.html

语言

Reference JDK Version Description Project&Tags
JEP 395: Records 16 充当不可变数据的载体的类,与元组的形式类似,拥有 极简单的类结构,有一些序列化相关的特性需要注意。 Amber - language
JEP 430: String Templates (Preview) 21 提供了一种字符串模板和嵌入表达式生成字符串的方式,并且 性能接近使用+来进行拼接!JMH测试结果:
StringConcatenationBenchmark.svg
https://ionutbalosin.com/2024/02/jvm-performance-comparison-for-jdk-21/
目前打算重做这个功能,这也是我不建议开启预览功能的原因之一 https://mail.openjdk.org/pipermail/amber-spec-experts/2024-April/004106.html
Amber - language、performance
JEP 361: Switch Expressions
JEP 394: Pattern Matching for instanceof
JEP 440: Record Patterns
JEP 441: Pattern Matching for switch
14
16
21
21
switch表达式的简化,包含instanceof的类型转换简化,对多种类型的支持(包含Record)
另外还额外对Record的模式匹配进行了简化,对嵌套的Record简化比较明显 https://belief-driven-design.com/looking-at-java-21-record-patterns-b5282/
Amber - language
JEP 286: Local-Variable Type Inference 10 笔者的建议是在特定情境下可以使用本特性,大部分是不关心具体的类型信息,或者可以在上下文中明确直接看到类型信息的时候。
例如在Stream中使用groupingBy的时候,这种时候类型信息会很长,但我们其实不太关注那一串具体的类型信息。
更具体的使用规范可以看 https://openjdk.org/projects/amber/guides/lvti-style-guide
Amber - language
JEP 378: Text Blocks 15 可以使用三个双引号 """text""" 包含多行字符串 Amber - language
JEP 409: Sealed Classes 17 密封类,可以 限制哪些其他类或接口可以继承或实现它们。 Amber - specification
JEP 431: Sequenced Collections 21 提供了一套 有序集合接口标准https://www.infoq.com/news/2023/03/collections-framework-makeover/ Amber - specification
JEP 309: Dynamic Class-File Constants 11 提供了类似 invokedynamic 的常量创建形式,一种使用场景是通过这种方式实现高性能懒加载的单例 Valhalla
JEP 443: Unnamed Patterns and Variables (Preview) 21 对于没有用的变量可以用_代替命名表示省略
https://www.baeldung.com/java-unnamed-patterns-variables
Amber - language

JVM&Compiler

Reference JDK Version Description
ZGC(JEP377) & Shenandoah(JEP379) 15 ZGC和Shenandoah发布了生产版本
JEP 439: Generational ZGC 21 ZGC支持分代了,提升很大,但是目前还存在一些bug
JEP 307: Parallel Full GC for G1 10 G1支持并发的fullgc了,16GB堆也可以在秒级以内了
JEP 158: Unified JVM Logging
JEP 271: Unified GC Logging
9 规范化了虚拟机里日志的打印,可以参考 OpenJDK 11 JVM日志相关参数解析与使用
JEP 345: NUMA-Aware Memory Allocation for G1 14 支持了对NUMA架构的感知,性能提升较大
JEP 280: Indify String Concatenation 9 编译时通过invokedynamic实现字符串拼接(以前是stringBuilder),可以更灵活地实现字符串拼接了,另外也为多语言铺路
JEP 312: Thread-Local Handshakes 10 线程局部握手,对偏向锁之类的场景可以不用等全局安全点了(偏向锁只是一个举例,后面被干掉了)
JEP 451: Prepare to Disallow the Dynamic Loading of Agents 21 jdk准备在将来的版本中默认禁止对正在运行的JVM加载agent(启动时可以)
JEP 346: Promptly Return Unused Committed Memory from G1 12 G1会更好地返还内存给操作系统(但对我们没用)
JEP 344: Abortable Mixed Collections for G1 12 G1能够通过划分必须回收区域、非必需回收区域来更精确地控制MixedGC的耗时。
JEP 318: Epsilon: A No-Op Garbage Collector (Experimental) 11 无GC的收集器,这玩意一般用不上,也只打算作为实验特性,开发者担心用户不小心误用,还是作为实验特性的好

Other

Reference JDK Version Description Project&Tags
JEP 444: Virtual Threads 21 Loom
JEP 446: Scoped Values (Preview) 21 能在许多场景下替代ThreadLocal,但注意! 它并不是为了取代 ThreadLocal !也取代不了
ThreadLocal变量修改难以追踪,难以调试;
容易忘记remove导致内存泄漏;
使用InheritableThreadLocal在子线程中传递父线程变量时, API 要求在其他线程中看不到更改线程局部变量的线程副本,因此会做拷贝逻辑 。

Scoped Value管理的变量不可变,代码更易读,并且通过不可变特性避免了拷贝的内存开销;
作用域更明确,生命周期结束后自然回收,内存更安全。

由于二者设计逻辑不同,依然存在一些场景适配工作量较大,例如将threadLocal当作资源池的场景。
最典型的,例如jackson的资源池,log4j2的ReusableMessage https://github.com/apache/logging-log4j2/pull/1401
Loom
JEP 453: Structured Concurrency (Preview) 21 https://www.infoq.com/news/2023/06/structured-concurrency-jdk-21/
例如
Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier<String>  user  = scope.fork(() -> findUser());
        Supplier<Integer> order = scope.fork(() -> fetchOrder());

    scope.join()            // Join both subtasks
         .throwIfFailed();  // ... and propagate errors

    // Here, both subtasks have succeeded, so compose their results
    return new Response(user.get(), order.get());
}
//...

}

结构化并发提供了一种任务组的结构化组织方式,我们可以比较灵活地处理这些任务,这种方式与虚拟线程的适配是很好的;
通过拓展StructuredTaskScope类,我们可以很灵活地定义自己想要的业务逻辑,并且可以将任务编排与业务逻辑分离开。
Loom
JEP 412: Foreign Function & Memory API (Incubator) 17 Panama对于JDK的影响其实比Loom可能更大(对我们上层开发者的影响不及Loom),感兴趣的可以看看相关的,Panama/JNI/JNA/JNR https://glavo.site/blog/2023/03/25/java-ffi-benchmark/ Panama
JEP 448: Vector API (Sixth Incubator) 21 支持SIMD向量化加速计算,至于什么是SIMD可以去百度一下,基本思想是通过 元素合并实现 在一个CPU周期内实现多个元素计算的并行
相关的还有个很火的项目 https://github.com/simdjson/simdjson ,使用SIMD加速JSON解析
也有java版本的 https://github.com/simdjson/simdjson-java
Panama
JEP 358: Helpful NullPointerExceptions 14 能够更准确地提供空指针异常信息
JEP 321: HTTP Client API 11 jdk提供了一套标准的HTTPClient的api
JEP 349: JFR Event Streaming 14
JEP 259: Stack-Walking API 9 在stackWalker之前只能通过 getStackTrace获取完整的线程堆栈,stackWalker允许通过StackFrame的流来执行想要的操作,这允许我们获取一部分堆栈,甚至能拿到堆栈上持有锁(monitor)的情况、局部变量、以及当前帧是编译帧还是解释帧等非常详细的信息。是个很实用的api。
JEP 250: Store Interned Strings in CDS Archives
JEP 310: Application Class-Data Sharing
JEP 341: Default CDS Archives
JEP 350: Dynamic CDS Archives
9
10
12
13
类数据共享存档,主要是需要提前构建好存档有点麻烦,使用可以参考 https://www.morling.dev/blog/building-class-data-sharing-archives-with-apache-maven/
JEP 193: Variable Handles 9 通过VarHanlde可以实现更细粒度的内存屏障控制,例如Caffeine中就通过VarHandle来维护时间戳。 Code Tools
JEP 254: Compact Strings 9 String的底层从char变成了byte,粒度更细了,占用内存可以变少一些。但是特定场景下可能会增加cpu开销。 https://ionutbalosin.com/2018/06/compact-strings-feature-might-slow-down-predominant-utf-16-strings-applications/
JEP 285: Spin-Wait Hints 9 自旋的场景使用 Thread.onSpinWait()可以节省CPU资源,例如
         volatile boolean eventNotificationNotReceived;
         void waitForEventAndHandleIt() {
             while ( eventNotificationNotReceived ) {
                 Thread.onSpinWait();
             }
             readAndProcessEvent();
         }
onSpinWait可以适当降低自己的优先级,降低对CPU资源的消耗
但值得注意的是, onSpinWait在命中C2之前都是空实现的逻辑,等于啥也没干,直到C2优化后才能发挥特性
https://bugs.openjdk.org/browse/JDK-8147844
JEP 416: Reimplement Core Reflection with Method Handles 18 使用MethodHandles重新实现了反射(部分场景变快,部分变慢,具体见JEP描述,主要目的是规范化)。APIMethodHandle在JDK7中引入,在使用方法上比反射复杂一些,限制也比反射多一点,但是正确的使用可以使性能接近非反射直接调用方法。PR: https://github.com/openjdk/jdk/pull/5027
顺带一提,Java表现出来了明显的倾向,想要尽量消除或者屏蔽C代码,想要尽可能替换成Java的实现。
Loom

废弃&删除

Reference JDK Version Description
JEP 374: Deprecate and Disable Biased Locking 15 废弃了偏向锁,这部分代码非常复杂,维护起来很麻烦,并且大部分情况下对性能并没有好处
JEP 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector 14 移除了CMS收集器
JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination 14 废弃了ParallelScavenge + SerialOld GC的组合
JEP 410: Remove the Experimental AOT and JIT Compiler 17 移除了实验性质的AOT(jaotc tool)和 JIT编译器,这部分工作和GraalVM一样使用Graal项目,但是没什么人用,还要单独去维护它。
因此直接移除了,并且建议需要使用 Graal 编译器用于 AOT 或 JIT的直接去用GraalVM即可。
JEP 214: Remove GC Combinations Deprecated in JDK 8 9 删除了以下GC组合
  • DefNew + CMS
  • ParNew + SerialOld
  • Incremental CMS

其他分类

jdk版本控制

JEP 322: Time-Based Release Versioning

重新规范了JDK的发版规则,更多jdk发版有关的信息可以参考 https://whichjdk.com/

发布模型的变更可以看这两篇

https://blogs.oracle.com/javamagazine/post/java-long-term-support-lts

https://blogs.oracle.com/java/post/moving-the-jdk-to-a-two-year-lts-cadence

Project Jigsaw 项目-模块化

这里简单说下,一般升级时需要关注export和opens

export是模块内部会向外export一些包,而不被模块主动export的则认为是随时可变的,不做任何保证。如果使用了模块没有向外export的API,则会在编译期报错,需要在编译和启动参数手动增加export参数。

opens是运行期的检查,对应反射访问模块API的情况,根据运行期间的报错加一下就好,这个需要加在启动参数上。

值得注意的是,在jdk9开始做模块化以后,通过 –illegal-access来做了运行时的模块化控制,也就是opens的统一控制

  • --illegal-access=permit : JDK9以后默认模式,只在第一次非法反射访问时 warning一下

  • --illegal-access=warn :与 permit类似,不过每次非法反射都会 warning

  • --illegal-access=debug :在warn的基础上,加上堆栈输出

  • --illegal-access=deny : 拒绝所有非法反射访问内部api,JDK16下的默认模式

但是在JDK17中,通过 JEP 403: Strongly Encapsulate JDK Internals 强封装 --illegal-access这个参数被无效化了,相当于只能deny,我们只能通过 opens来控制运行时反射的访问。

适配&兼容

JEP 247: Compile for Older Platform Versions

编译时可以通过source和target参数灵活控制产物jdk版本,对兼容老版本还是有好处的。

JEP 238: Multi-Release JAR Files

可以指定不同jdk版本的环境中使用不同的api。例如我维护了一个jar包,其中一个接口在jdk8下希望使用某个api,在jdk21下使用另一个api,就可以通过 Multi-Release JAR 实现,不过这个东西实际比较鸡肋。

不在JEP内的

Java 18, ZGC, SerialGC, and ParallelGC support string deduplication

https://blogs.oracle.com/javamagazine/post/java-18-gems-hidden-subtle-changes-deprecations

jakarta

https://wiki.eclipse.org/Jakarta_EE_Maven_Coordinates

参考

https://blogs.oracle.com/java/post/the-arrival-of-java-21

https://cn.quarkus.io/blog/virtual-thread-1/

https://chriswhocodes.com/jepmap.html