JDK 21 是继 JDK 17 之后的又一个 LTS 版本,虚拟线程转正、分代 ZGC 落地、Record Patterns 和 Pattern Matching for switch 正式定型,这几个特性凑在一起,让它成了近几年最值得升级的一个版本。但问题也随之而来:很多人兴冲冲装完 JDK 21,打开 IDEA 却发现语言级别最高只到 17,或者项目能编译但一跑就报Unsupported class file major version 65,再或者虚拟线程的 API 死活提示找不到符号。这些坑我在最近几个项目里几乎挨个踩了一遍,所以这篇文章就把 IntelliJ IDEA 2023.2 配置 JDK 21 的完整流程、每个环节背后的逻辑、以及实战新特性时容易翻车的地方,一次性讲透。不管你是刚接触 IDEA 的新手,还是用了好几年但一直没搞明白 SDK、语言级别、字节码版本这三者关系的老手,看完都能直接照着操作,把 JDK 21 跑起来。
1. 升级前的整体思路与版本选型
1.1 为什么偏偏是 IDEA 2023.2 配 JDK 21
先说版本搭配这件事,因为它决定了你后面会不会白折腾。JDK 21 正式发布于 2023 年 9 月,而 IDEA 2023.2 是同年 7 月底的版本,严格来说它发布时 JDK 21 还没 GA。那为什么还推荐这个组合?因为 IDEA 2023.2 已经内置了对 JDK 21 的预览支持,尤其是对虚拟线程调试、结构化并发这些特性的识别做得比较到位,后续的 2023.2.x 小版本又补了不少兼容性修复。如果你用的是 2023.1 或更早,配置 JDK 21 时语言级别下拉框里可能压根找不到 21 这个选项,只能手动填,体验很差。
这里有个关键认知要先建立起来:IDEA 版本、JDK 版本、项目语言级别、字节码版本,这是四个独立的东西。很多人把它们混为一谈,导致配置时顾此失彼。IDEA 版本决定它“认不认识”某个 JDK;JDK 版本是你实际安装的运行时;语言级别决定编译器允许你用哪些语法;字节码版本决定编译出来的 class 文件能被哪个版本的 JVM 加载。后面所有的坑,本质上都是这四个东西没对齐。
1.2 JDK 21 到底带来了哪些值得升级的东西
如果只是版本号好看,那没必要折腾。JDK 21 真正吸引人的是几个转正的重磅特性。虚拟线程(Virtual Threads)从预览转正,这是 Project Loom 的核心成果,用极轻量的线程模型把高并发 IO 场景的吞吐量拉高一个量级,而且写法几乎和普通线程一样。分代 ZGC转正,把 ZGC 的停顿时间优势扩展到了分代场景,对大堆内存应用很友好。语言层面,Record Patterns和Pattern Matching for switch正式定型,模式匹配终于可以放心用在生产代码里。还有Sequenced Collections这个新集合接口,给有序集合补上了统一的头尾操作 API。
这些特性里,虚拟线程是绝大多数人升级 JDK 21 的直接动机,也是配置过程中最容易出问题的地方——因为它涉及 API 可用性和运行时行为的双重变化,光把语言级别调到 21 还不够。
1.3 升级前必须确认的三件事
动手之前,先花五分钟确认三件事,能省掉后面大量返工。第一,确认你现有的项目依赖有没有和 JDK 21 冲突的,特别是一些老版本的字节码增强库(比如某些版本的 ASM、CGLIB),它们可能不认识 major version 65 的 class 文件。第二,确认构建工具版本,Maven 需要 3.9.x 以上、Gradle 需要 8.4 以上才对 JDK 21 有完整支持,版本太低会在编译阶段就报错。第三,确认团队协作环境,如果项目里有人还在用 JDK 8 或 11,那你本地升到 21 编译出来的产物可能别人跑不了,这种情况要么统一升级,要么用工具链(Toolchain)隔离。
提示:升级 JDK 大版本前,强烈建议先在独立分支上验证,不要直接在主开发分支上动。JDK 21 虽然兼容性做得不错,但字节码版本提升后,任何依赖字节码操作的库都可能出问题。
2. JDK 21 的下载安装与环境变量配置
2.1 选哪个发行版:别只盯着一个来源
JDK 21 有很多发行版,常见的有 Oracle JDK、Eclipse Temurin(Adoptium)、Amazon Corretto、Azul Zulu、Microsoft Build of OpenJDK 等。它们都基于同一套 OpenJDK 源码,核心功能一致,区别主要在许可证、长期维护承诺和附带的工具上。对于个人开发和大多数企业项目,Eclipse Temurin是比较稳妥的选择,社区活跃、更新及时、没有许可证顾虑。如果你在公司环境里,可能已经有统一的发行版规范,那就跟着规范走。
选发行版时要注意一点:不同发行版对虚拟线程的默认调度参数可能略有差异,虽然 API 层面完全一致,但在压测时表现会有细微不同。这个差异不影响开发,但如果你要做性能基准测试,最好固定用一个发行版,别混着测。
2.2 安装路径的选择与命名习惯
安装路径这件事看似小事,其实影响很大。我见过太多人把 JDK 装在带空格或中文的路径下,比如C:\Program Files\Java\jdk-21或者D:\开发工具\jdk21,然后在配置 IDEA 或构建工具时各种报路径错误。强烈建议用纯英文、无空格、层级浅的路径,比如D:\dev\jdk\jdk-21或/opt/jdk/jdk-21。
另外,如果你机器上同时有多个 JDK 版本(这几乎是必然的),建议在路径命名上带上版本号,比如jdk-17、jdk-21,而不是笼统的jdk。这样后面配置环境变量和 IDEA SDK 时一目了然,不会搞混。我自己习惯在D:\dev\jdk\下按版本建目录,切换时改环境变量或者直接在 IDEA 里指定路径就行。
2.3 环境变量的配置逻辑
环境变量配置是新手最容易出错的地方,核心是要理解JAVA_HOME和PATH的分工。JAVA_HOME指向 JDK 的根目录,很多工具(包括 IDEA、Maven、Gradle)都靠它来定位 JDK;PATH里则要加入%JAVA_HOME%\bin(Windows)或$JAVA_HOME/bin(Linux/macOS),这样命令行才能直接调用java、javac等命令。
Windows 下的配置步骤大致是这样:新建系统变量JAVA_HOME,值填D:\dev\jdk\jdk-21;然后在Path变量里新增一条%JAVA_HOME%\bin,注意要把它移到其他 Java 相关路径的前面,否则可能被旧版本覆盖。Linux/macOS 下则是在~/.bashrc或~/.zshrc里加两行:
export JAVA_HOME=/opt/jdk/jdk-21 export PATH=$JAVA_HOME/bin:$PATH配置完记得source一下配置文件,然后开个新终端验证。
2.4 验证安装是否成功
验证不能只看java -version输出,那个只能说明 PATH 配对了,不能说明JAVA_HOME正确。完整的验证要跑三条命令:
java -version javac -version echo $JAVA_HOME # Windows 下用 echo %JAVA_HOME%三条命令的输出版本号必须一致,都是 21。如果java -version是 21 但javac -version是 17,说明 PATH 里有旧版本的 javac 排在前面,得去调整顺序。如果JAVA_HOME输出为空或指向旧版本,那 IDEA 里配置 SDK 时就会出问题。这一步千万别偷懒,我见过太多“明明装了 21 但 IDEA 里就是认不到”的情况,最后都是环境变量没配对。
注意:Windows 下修改环境变量后,已经打开的终端和 IDEA 不会自动刷新,必须重启它们才能读到新值。这个细节坑过不少人。
3. 在 IDEA 2023.2 中配置 JDK 21
3.1 全局 SDK 与项目 SDK 的区别
IDEA 里的 SDK 配置分两个层级:全局 SDK(Platform Settings 里的 SDKs)和项目 SDK(Project Structure 里的 Project SDK)。全局 SDK 是给整个 IDE 用的,配置一次,所有项目都能选;项目 SDK 是给当前项目用的,决定这个项目编译和运行时用哪个 JDK。很多人只配了全局 SDK,结果项目里还是指向旧的,就是因为没在项目层级再选一次。
正确的做法是先在全局 SDK 里把 JDK 21 加进来,然后在每个需要升级的项目里,把 Project SDK 指向它。这样既统一了管理,又能按项目灵活切换。如果你的项目用 Maven 或 Gradle,还要注意构建工具自己的 JDK 配置,它可能和 IDEA 的配置是分开的,后面会细说。
3.2 添加 JDK 21 到全局 SDK 列表
打开 IDEA,进File -> Project Structure(快捷键Ctrl+Alt+Shift+S),左侧选Platform Settings -> SDKs。点左上角的+号,选Add JDK,然后浏览到你 JDK 21 的安装目录,比如D:\dev\jdk\jdk-21。选中后 IDEA 会自动识别版本号,显示成21。给它起个清晰的名字,比如jdk-21,别用默认的21或者corretto-21这种,方便后面区分。
添加完成后,你会在 SDK 列表里看到多个版本。这时候可以顺手检查一下每个 SDK 的Sourcepath和Documentation Path有没有配好,配好了以后看 JDK 源码和文档会方便很多。不过这个不是必须的,不影响编译运行。
3.3 项目 SDK 与语言级别的对齐
回到Project Structure的Project页面,这里有两个关键下拉框:SDK和Language level。SDK选刚加的jdk-21,Language level选21 - Record patterns, pattern matching for switch。这两个必须对齐,如果 SDK 选了 21 但语言级别还是 17,那你写虚拟线程的代码时 IDEA 会提示语法不支持;反过来语言级别选了 21 但 SDK 是 17,编译时会报invalid target release: 21。
这里有个容易忽略的点:语言级别可以高于 SDK 版本,但那样编译一定失败。IDEA 允许你这么选,但不会阻止你,所以配置完最好看一眼两个下拉框是不是都是 21。另外,Project Structure里还有Modules页面,每个模块也可以单独设语言级别,如果模块级别和项目级别不一致,以模块级别为准。多模块项目里这个坑很常见。
3.4 编译器字节码版本的设置
光配好 SDK 和语言级别还不够,还得确认编译器的字节码版本。进Settings -> Build, Execution, Deployment -> Compiler -> Java Compiler,看看Project bytecode version是不是 21。如果这里是空的或者旧版本,编译出来的 class 文件版本就不对,运行时可能报Unsupported class file major version。
字节码版本和语言级别的关系是这样的:语言级别决定你能用哪些语法,字节码版本决定编译产物能被哪个 JVM 加载。正常情况下两者应该一致,都是 21。但有些特殊场景,比如你想用 JDK 21 编译但让产物能在 JDK 17 上跑(这叫交叉编译),那语言级别和字节码版本可以设成 17,不过这种场景很少见,而且容易出兼容问题,不建议新手尝试。
3.5 Maven 和 Gradle 项目的额外配置
如果你的项目是 Maven 或 Gradle 构建的,光在 IDEA 里配还不够,构建脚本里也得改,否则命令行构建或者 CI 上会失败。Maven 项目要在pom.xml里配置maven-compiler-plugin:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>21</source> <target>21</target> <release>21</release> </configuration> </plugin>这里推荐用<release>21</release>而不是单独的source和target,因为release参数会同时校验 API 和字节码版本,更严格也更安全。Gradle 项目则在build.gradle里设置:
java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 }或者用工具链的方式:
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }工具链的好处是它会自动定位本地的 JDK 21,不依赖环境变量,团队协作时更稳。
提示:Maven 的
maven-compiler-plugin版本要 3.11.0 以上,Gradle 要 8.4 以上,低版本对 JDK 21 支持不完整,会在编译时报奇怪的错误。
4. JDK 21 新特性实战与踩坑记录
4.1 虚拟线程:写法、调度与常见误区
虚拟线程是 JDK 21 最受关注的特性,它的核心价值在于用极低的资源开销支撑海量并发。传统平台线程(Platform Thread)一个线程对应一个操作系统线程,创建成本高、内存占用大,几千个就到顶了;虚拟线程由 JVM 调度,一个平台线程可以承载成千上万个虚拟线程,特别适合 IO 密集型的场景,比如处理大量 HTTP 请求、数据库查询等。
创建虚拟线程的写法很直接:
Thread vThread = Thread.ofVirtual().start(() -> { System.out.println("运行在虚拟线程上: " + Thread.currentThread()); }); vThread.join();或者用Executors提供的工厂:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10000).forEach(i -> { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); }这段代码创建了一万个任务,每个任务睡一秒,用虚拟线程跑几乎瞬间完成,换成平台线程早就 OOM 了。
但这里有几个坑必须说清楚。第一,虚拟线程不适合 CPU 密集型任务,因为它最终还是跑在平台线程上的,CPU 密集场景用虚拟线程没有收益,反而增加调度开销。第二,不要在虚拟线程里用synchronized做长时间阻塞,因为synchronized会 pin 住载体线程(Carrier Thread),导致其他虚拟线程无法调度,这个叫“固定”(Pinning)。JDK 21 里应该用ReentrantLock替代。第三,线程池不要复用虚拟线程,虚拟线程的设计就是一次性的,用完即弃,用池化反而违背了它的设计初衷。
注意:
Thread.sleep在虚拟线程里是可以正常让出载体线程的,但synchronized块里的阻塞不行。如果你发现虚拟线程的吞吐量没达到预期,先检查代码里有没有synchronized包裹的 IO 操作。
4.2 Record Patterns 与模式匹配实战
Record Patterns 让解构 record 变得非常优雅。以前要从一个 record 里取值,得先判断类型再逐个调 accessor;现在可以直接在instanceof或switch里解构:
record Point(int x, int y) {} record Line(Point start, Point end) {} Object obj = new Line(new Point(0, 0), new Point(3, 4)); if (obj instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) { System.out.println("起点: " + x1 + "," + y1 + " 终点: " + x2 + "," + y2); }嵌套解构也能一层层拆开,代码可读性提升非常明显。配合switch的模式匹配,可以写出很清晰的类型分发逻辑:
String describe(Object obj) { return switch (obj) { case Integer i when i > 0 -> "正整数: " + i; case Integer i -> "非正整数: " + i; case String s -> "字符串: " + s; case null -> "空值"; default -> "其他类型"; }; }这里有个细节要注意:switch的模式匹配里null必须显式处理,否则会抛NullPointerException。这是 JDK 21 的新行为,和以前的switch不一样。另外,when子句(guard)的顺序很重要,范围窄的条件要写在前面,否则会被宽条件先匹配掉。
4.3 Sequenced Collections 的实用场景
Sequenced Collections 引入了SequencedCollection、SequencedSet、SequencedMap三个接口,给有序集合补上了统一的头尾操作。以前想拿List的第一个元素用get(0),拿Deque的第一个用getFirst(),拿LinkedHashSet的第一个得用迭代器,接口不统一。现在都统一成getFirst()、getLast()、addFirst()、addLast()、reversed()了。
List<String> list = new ArrayList<>(List.of("a", "b", "c")); System.out.println(list.getFirst()); // a System.out.println(list.getLast()); // c System.out.println(list.reversed()); // [c, b, a]reversed()返回的是视图而不是拷贝,对它的修改会反映到原集合上,这个特性在做栈、队列操作时很方便。不过要注意,reversed()返回的视图在并发修改时行为和原集合一致,该加锁还是得加锁。
4.4 分代 ZGC 的启用与观察
分代 ZGC 是 JDK 21 里另一个重量级特性,它把 ZGC 的低停顿优势扩展到了分代回收场景。启用方式很简单,加启动参数:
-XX:+UseZGC -XX:+ZGenerational注意 JDK 21 里分代 ZGC 还需要显式加-XX:+ZGenerational,到了后续版本才默认开启。启用后可以用-Xlog:gc*观察 GC 日志,看看停顿时间和吞吐量的变化。分代 ZGC 特别适合大堆内存(几十 GB 以上)且对停顿敏感的应用,比如实时交易、在线游戏等。但如果你的堆很小(几 GB),用默认的 G1 可能更划算,因为 ZGC 的额外开销在小堆上体现不出来。
提示:分代 ZGC 在 JDK 21 里还是相对新的特性,生产环境启用前建议做充分的压测,观察内存占用和 CPU 使用率的变化。
5. 常见问题排查与避坑速查
5.1 编译与运行时的版本类错误
配置 JDK 21 过程中最常见的错误都和版本不匹配有关,我把它们整理成一张速查表,遇到问题直接对号入座。
| 错误信息 | 根本原因 | 解决方法 |
|---|---|---|
Unsupported class file major version 65 | 运行时 JVM 版本低于 21,加载不了 21 编译的 class | 确认运行配置里的 JRE 是 21,检查JAVA_HOME |
invalid target release: 21 | 编译器 JDK 低于 21 | 检查 IDEA 的 Build Tools JDK 和 Maven/Gradle 的 JDK |
cannot find symbol: method ofVirtual() | 语言级别低于 21 | 把 Project Language Level 调到 21 |
package java.lang does not exist | 模块路径或 SDK 配置错误 | 重新配置 Project SDK,检查模块依赖 |
class file has wrong version 65.0 | 依赖库是用 21 编译的,但项目用低版本 JDK | 统一项目 JDK 到 21,或降级依赖库 |
这张表里的错误我几乎都遇到过,其中Unsupported class file major version 65出现频率最高,十有八九是运行配置里的 JRE 没改。IDEA 的运行配置(Run Configuration)里有个单独的 JRE 选项,默认可能是项目 SDK,但如果你手动改过,它就不会跟着项目 SDK 变,得单独检查。
5.2 IDEA 识别不到 JDK 21 的排查
有时候明明装好了 JDK 21,环境变量也配了,但 IDEA 的 SDK 列表里就是找不到,或者添加时报错。这种情况按下面的顺序排查:先确认 IDEA 是不是从终端启动的,如果是,它可能继承了旧的环境变量,重启 IDEA 试试;再确认 JDK 安装目录下有没有release文件和bin/javac,有些精简版 JDK 缺这些文件,IDEA 认不出来;最后检查 IDEA 的Build Tools -> Maven -> Runner里的 JRE 设置,这里如果指向旧版本,Maven 构建会用旧 JDK。
还有一个隐蔽的坑:IDEA 的缓存。有时候配置改对了但 IDEA 还是用旧值,这时候进File -> Invalidate Caches清一下缓存重启,往往就好了。这个操作我每个月都要用几次,尤其是切换 JDK 版本之后。
5.3 虚拟线程调试的特殊注意事项
虚拟线程的调试和普通线程不太一样。在 IDEA 的调试器里,虚拟线程默认可能不会全部显示,因为数量太多会拖垮调试器。IDEA 2023.2 对虚拟线程调试做了优化,可以在调试窗口里过滤和折叠。但要注意,在虚拟线程里打断点可能会影响调度行为,因为断点会让线程暂停,进而影响载体线程的调度。调试虚拟线程相关代码时,建议用日志而不是断点,或者只在关键路径上打断点。
另外,虚拟线程的堆栈跟踪和平台线程不同,它显示的是逻辑调用栈,不包含载体线程的信息。如果你需要看虚拟线程跑在哪个载体线程上,得用Thread.currentThread()打印,或者用 JFR(Java Flight Recorder)事件来观察。JFR 对虚拟线程的支持在 JDK 21 里已经比较完善了,jdk.VirtualThreadStart和jdk.VirtualThreadEnd事件可以帮你分析虚拟线程的生命周期。
5.4 多模块项目的版本一致性维护
多模块项目里,最容易出的问题是各个模块的 JDK 版本不一致。父 POM 里配了 21,但某个子模块的pom.xml里又写了 17,构建时就会混乱。我的做法是在父 POM 里用<properties>统一定义版本,子模块引用属性而不是写死:
<properties> <maven.compiler.release>21</maven.compiler.release> <java.version>21</java.version> </properties>Gradle 多模块项目则可以在根build.gradle里用subprojects或allprojects统一配置,或者用java-toolchain插件在每个模块里声明工具链。关键是只在一个地方定义版本,其他地方引用,避免散落各处导致不一致。
提示:IDEA 的
Modules页面里每个模块的语言级别也要检查一遍,有时候 Maven 重新导入后,模块级别会被重置成默认值,导致编译报错。
6. 从 JDK 17 平滑迁移到 21 的实操建议
6.1 迁移前的依赖兼容性扫描
从 JDK 17 迁到 21,大部分代码不用改,但依赖库需要过一遍。重点检查这几类:字节码操作库(ASM、ByteBuddy、CGLIB)、反射框架(Spring、Hibernate 的某些版本)、序列化库(Jackson、Kryo)。这些库如果版本太老,可能不认识 major version 65 的 class 文件,或者用了 JDK 21 里被移除的内部 API。
扫描方法很简单,先把项目 JDK 切到 21 编译一遍,看有没有编译错误;然后跑一遍完整的测试套件,看运行时有没有异常。如果某个依赖报错,先查它的最新版本有没有支持 JDK 21,有就升级,没有就找替代方案。Spring Boot 3.x 对 JDK 21 支持很好,如果还在用 Spring Boot 2.x,迁移 JDK 21 之前最好先升到 3.x。
6.2 分阶段迁移的节奏控制
一次性把所有模块都升到 21 风险太大,建议分阶段来。第一阶段,只升级本地开发环境的 JDK 和 IDEA 配置,代码不动,验证编译和测试能过;第二阶段,把构建脚本里的版本号改成 21,在 CI 上跑一遍,确认构建产物没问题;第三阶段,逐步在新代码里使用 JDK 21 的新特性,老代码保持不动;第四阶段,等新特性用稳了,再回头重构老代码。
这个节奏的好处是每一步都可回退,出问题能快速定位是哪个环节引入的。我见过有人一步到位全升,结果一个依赖库不兼容,整个项目卡住好几天。
6.3 团队协作中的版本约定
团队里每个人的环境不一样,光靠口头约定“大家都用 21”是不够的。我的做法是在项目根目录放一个.java-version文件(配合版本管理工具),或者用 Maven/Gradle 的工具链配置强制指定版本。工具链的好处是它会自动下载或定位指定版本的 JDK,不依赖开发者本地装了什么,CI 上也能保证一致。
另外,在 README 或者贡献指南里写清楚 JDK 版本要求、构建命令、常见问题,新成员上手时能少踩很多坑。这些文档看起来不起眼,但团队规模一大,省下的沟通成本非常可观。
6.4 回退方案与风险控制
升级过程中一定要留好回退路径。最直接的做法是在版本控制里开一个升级分支,主分支保持不动,等升级分支验证稳定了再合并。如果项目已经用了容器化部署,可以在 Dockerfile 里固定 JDK 版本,回退时改一行就行。
还有一点容易被忽略:数据库驱动、消息队列客户端这类和运行时环境强相关的依赖,升级 JDK 后要重点测试。它们可能用了 JDK 内部 API,或者对字节码版本敏感。我遇到过一次升级 JDK 21 后消息队列客户端连接异常,排查半天发现是客户端依赖的一个底层库版本太老,升级后就好了。
7. 我个人的配置习惯与效率技巧
7.1 用 SDK 命名规范减少切换成本
我在 IDEA 里给每个 JDK 都起了带版本和发行版的名字,比如temurin-17、temurin-21、corretto-21,而不是默认的17、21。这样在项目里切换 SDK 时,一眼就能看出用的是哪个发行版,避免“这个 21 到底是哪个 21”的困惑。多项目并行开发时,这个习惯能省不少事。
另外,我会把常用的 JDK 路径记在一个笔记里,配置新机器或者帮同事排查时直接复制,不用每次去翻安装目录。这种小习惯积累起来,效率提升很明显。
7.2 快捷键与配置模板的复用
IDEA 的Project Structure对话框可以用Ctrl+Alt+Shift+S快速打开,配置 JDK 时我基本不点菜单。语言级别和字节码版本的设置,我会在新建项目时就配好,然后导出成项目模板,下次新建直接套用,不用重复配置。
对于 Maven 项目,我会把maven-compiler-plugin的配置写进一个公司内部的父 POM,所有项目继承,这样新项目天然就是 JDK 21 配置,不用每个项目单独改。这个做法在团队里推广后,配置相关的求助少了一大半。
7.3 保持 IDEA 与 JDK 版本同步更新
JDK 和 IDEA 都在持续更新,小版本里往往包含重要的兼容性修复。我的习惯是每季度检查一次 IDEA 和 JDK 的小版本更新,在测试环境验证后升级。特别是 JDK 的补丁版本,安全修复和性能优化都值得跟进。但要注意,不要在生产环境直接用最新版本,等它稳定一两个补丁版本再上,这是血泪教训。
最后分享一个小技巧:如果你不确定某个新特性在当前配置下能不能用,可以写一个最小的测试类,用jshell直接跑一下。jshell是 JDK 自带的交互式工具,启动快、验证方便,比在 IDEA 里建项目试要高效得多。配置 JDK 21 后,jshell也会跟着用 21,直接在里面敲虚拟线程的代码就能验证环境对不对。