技术分享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
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
语言
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/ 例如 结构化并发提供了一种任务组的结构化组织方式,我们可以比较灵活地处理这些任务,这种方式与虚拟线程的适配是很好的;通过拓展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资源,例如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组合
|
其他分类
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