Java 21 LTS 新特性深度解读:虚拟线程 + 分代 ZGC + 序列集合 + Switch 模式匹配
§1 版本元数据
| 项目 | 值 |
|---|---|
| 官方名称 | Java SE 21 |
| 发布日 | 2023-09-19 |
| 类型 | LTS(Long-Term Support) |
| Oracle Premier Support 截止 | 2028-09(已延后;公开免费 2026-09 结束) |
| Oracle Extended Support 截止 | 2031-09 |
| 第三方 LTS | BellSoft Liberica 至 2032-03 / Azul Zulu 至 2032 |
| 关键里程碑 | 虚拟线程正式 + 分代 ZGC 正式 + 序列集合 + Switch 模式匹配 + Record Patterns |
| JEP 总数 | 约 15 个 JEP(21uXX 累计更多) |
| 同期生态 | OpenJDK 21 + Eclipse Adoptium Temurin 21 + Spring Boot 3.2+(默认开虚拟线程) |
为什么 Java 21 是 Java 8 以来最值得升级的 LTS:Java 21 的虚拟线程让 Java 在高并发场景下重新具备竞争力——百万级并发成为可能,不再被 Go / Node.js / Kotlin Coroutine 在云原生场景拉开差距。分代 ZGC 把最大堆 16TB 跟暂停时间 < 1ms 兼得,大型微服务 / 大数据平台第一次有了无短板的 GC 选项。Spring Boot 3.2+ 默认开启虚拟线程(spring.threads.virtual.enabled=true),升级到 Java 21 + Spring Boot 3.2 是新项目的事实标准。
§2 重大新特性
2.1 虚拟线程(JEP 444)—— 百万级并发成为可能
问题:传统平台线程 1:1 映射 OS 线程,1MB 栈空间 × 10000 并发 = 10GB 内存。Go 协程 / Kotlin Coroutine 早就用 M:N 调度实现"百万并发",Java 在云原生高并发场景被拉开。
方案:虚拟线程由 JVM 调度到少量 OS 线程(M:N),栈占用从 MB 降到 KB,JVM 自身调度 + parking 让挂起的虚拟线程不占 OS 线程。
代码:
| |
收益:1 万并发任务从 5.5 秒降到 0.18 秒(31 倍加速)——见 §5 Demo 1。更重要的是可以并发百万级(Stack Overflow “Java + 100K 并发"经典问题 2024 年起有了 Java 原生答案)。Spring Boot 3.2+ 内置 spring.threads.virtual.enabled=true,Tomcat 默认线程池切到虚拟线程,10K 并发连接用 1 个平台线程即可。
2.2 分代 ZGC(JEP 439)—— 大堆 + 极低延迟
问题:传统 ZGC 把年轻代老年代同等对待,浪费性能——大多数对象"短命”(朝生夕死),让老年代扫描频率跟年轻代一样是浪费。
方案:分代 ZGC 区分新生代(短命对象)和老年代(长命对象),新生代用更激进的收集策略(频繁回收 + 短暂停),老年代用全量扫描(次数少 + 暂停 < 1ms)。最大堆支持 16TB。
代码(启用):
| |
收益:16TB 大堆 + 暂停 < 1ms + 吞吐量比 G1 高 10-20%。但 ZGC 在 32GB 以下小堆无明显优势(吞吐比 G1 低 5-10%)。ZGC 是大堆 + 极低延迟专用。
2.3 序列集合(JEP 431)—— List/Deque/SortedSet 统一首尾操作
问题:Java 集合"首尾"操作 API 长期混乱——List 用 add(0, e) / get(0),Deque 用 addFirst(e) / getFirst(),SortedSet 没有首尾直接操作(要先 iterator().next())。不同集合记不同 API 是负担。
方案:Java 21 引入 SequencedCollection / SequencedSet / SequencedMap 接口,统一 addFirst / addLast / getFirst / getLast / removeFirst / removeLast / reversed 等方法。List / Deque / LinkedHashSet 等都自动实现。
代码:
| |
收益:API 统一,少记 50% 集合方法;reversed() 返回反转视图(不复制数据,零拷贝);removeFirst / removeLast 比老的 remove(0) / remove(size-1) 快 2-5 倍(ArrayList 不用移动所有元素)。
2.4 Switch 模式匹配(JEP 441)—— 类型模式 + 守卫 + null 模式
问题:Java 17 引入传统 switch 表达式(不带 pattern binding),但模式匹配 switch 留到 Java 21 正式——之前的 case Circle c -> 是 Preview,生产不能用。
方案:switch 支持类型模式(case Circle c ->)、守卫(case Integer i when i > 0 ->)、null 模式(case null ->)。配合 Sealed Class 做编译期穷尽性检查。
代码:
| |
收益:when 守卫省掉 if-else 嵌套;case null -> 显式处理 null;Sealed 配合下漏 case 编译报错(漏 case 编译报错——比传统 switch 早失败 1 步)。
2.5 Record Patterns(JEP 440)—— Record 解构
问题:Record 是 Java 14 引入的不可变数据载体,但没法在模式匹配里"解构"——if (p instanceof Point p) 后还得 p.x() / p.y() 取字段。
方案:Record Patterns 允许在 instanceof / switch 里直接"解构 Record"——case Point(int x, int y) -> 直接拿到 x、y 局部变量。支持嵌套 Record 解构。
代码:
| |
收益:解构语法糖省掉 3-5 行 boilerplate;JSON 解析 / XML 解析 / AST 遍历都从中受益;JSON-Pointer 风格 API 设计第一次在 Java 里有原生支持。
2.6 字符串模板(JEP 459 Preview)—— 安全字符串拼接
问题:Java 字符串拼接要么 "a" + b + "c" 啰嗦,要么 String.format("User %s (id=%d)", name, id) 难读,没有原生"嵌入式表达式"语法。更严重:String.format 不知道参数是否可信,SQL 注入 / XSS 风险藏在 format 里。
方案:Java 21 引入 STR."..." 模板处理器,\{expr} 嵌入表达式;FMT 模板支持格式说明符;RAW 模板不做转义。第二次 Preview(Java 21 是第二次 Preview,Java 22 取消,Java 23/24 重新引入——spec §3 标注"不在 Java 25 列表")。
代码:
| |
收益:嵌入式语法更接近自然语言;FMT 模板可以写 SQL 安全拼接(SQL."SELECT * FROM users WHERE id = \{id}" 转义参数防注入);但 Preview 阶段生产慎用——JEP 459 在 Java 21 是 Preview 2,API 可能变。
§3 JVM 参数变更
| 变更类型 | 参数 | 说明 |
|---|---|---|
| 新增(正式) | -XX:+UseZGC -XX:+ZGenerational | 分代 ZGC(JEP 439) |
| 新增(正式) | --enable-preview 含虚拟线程 | 虚拟线程默认启用(不需 flag) |
| 新增(正式) | -XX:VirtualThreadWorkerThreadPriority=N | 虚拟线程载体线程优先级 |
| 新增 | -Djdk.tracePinnedThreads=full | 排查 synchronized pin 虚拟线程 |
| 废弃 | -XX:+UseG1GC 仍是默认 | G1 还是 server 模式默认 GC |
| 移除 | -XX:+UseConcMarkSweepGC(CMS) | 早已移除 |
启用分代 ZGC:
| |
§4 垃圾回收器演进
| GC | Java 21 状态 | 引入版本 | 关键调优 |
|---|---|---|---|
| Serial | 保留 | 1.0 | -XX:+UseSerialGC |
| Parallel | 保留(不再是默认) | 1.4 | -XX:+UseParallelGC |
| G1 | 默认(仍是 server 模式默认) | 7u4 | -XX:MaxGCPauseMillis=200 |
| ZGC | 正式 + 分代(JEP 439) | 11 实验 → 15 正式 → 21 分代 | -XX:+UseZGC -XX:+ZGenerational |
| Shenandoah | 保留 | 12 | -XX:+UseShenandoahGC |
| Epsilon | 保留 | 11 | -XX:+UseEpsilonGC |
| CMS | 已移除 | 1.4.1 | 不再可用 |
重点:
- 分代 ZGC 正式(JEP 439)—— 最大堆 16TB + 暂停 < 1ms
- G1 仍是 server 模式默认 GC(Java 17 起;Java 21 沿用)
- Shenandoah GC 由 Red Hat 维护(OpenJDK 12 引入,21 完善)
- ZGC 默认值尚未切换(Java 23/24 计划把分代 ZGC 设为默认,Java 21 仍 G1)
调优示例:
| |
§5 生产代码实战
Demo 1:虚拟线程 vs 平台线程池(10K 并发对比)
文件:assets/code/jdk-lts/Java-21/VirtualThreadVsThreadPoolDemo.java
场景:批量调用第三方 API 拉取订单(每个调用 100ms 模拟 I/O 等待)—— 1 万个 I/O 密集任务,对比平台线程池(200 并发)和虚拟线程(10K 并发)的耗时。
关键代码:
| |
跑通命令:
| |
预期输出(实测):
| |
为什么这个 demo 有工业价值:
- 展示虚拟线程 31 倍加速(10K 并发下)
- 平台线程池 200 并发就是上限(再高 OOM),虚拟线程可 10K / 100K
- 生产里"批量调用外部 API"场景直接换虚拟线程,性能立竿见影
Demo 2:序列集合 + Switch 模式匹配 + null 模式
文件:assets/code/jdk-lts/Java-21/SequencedCollectionsDemo.java
场景:统一集合首尾操作 + Java 21 Switch 模式匹配(带 pattern binding + null 模式)。
关键代码:
| |
跑通命令:
| |
预期输出:
| |
收益:序列集合 API 统一省记忆;Switch 模式匹配 + Sealed 编译期穷尽;case null 显式处理 null 避免 NPE。
§6 升级指南
从 Java 17 升 Java 21:
| 风险 | 缓解 |
|---|---|
虚拟线程里用 synchronized —— pin 住平台线程,性能退化 | 改用 ReentrantLock;加 -Djdk.tracePinnedThreads=full 排查 |
| 字符串模板(JEP 459)是 Preview 2 | 生产不用 Preview API(等正式或变 Preview 3 再说) |
| Switch 模式匹配 / Record Patterns 跟老代码互操作 | 业务代码层不影响;调用方代码不识别不影响运行时 |
| 字节码版本 61 → 65 | 老 .class 文件不需重新编译,但运行时要求升 JDK 21+ |
| 默认 GC 仍是 G1(分代 ZGC 是显式 flag) | 大堆 / 极低延迟项目主动加 -XX:+UseZGC -XX:+ZGenerational |
Spring Boot 3.2+ 默认开虚拟线程(spring.threads.virtual.enabled=true) | 升级后先关闭做对比测试,再开——确认业务代码无 synchronized pin |
升级 5 步走:
- 依赖扫描:检查是否有用到 Preview API(JEP 459 字符串模板)
- JUnit 测试:JDK 21 跑测试套,重点看 synchronized 用法(pin 虚拟线程)
- 灰度切流:单实例跑 1 周,监控 GC 日志(
-Xlog:gc*)+ 虚拟线程 pin 警告 - 全量切:灰度无问题后改默认 JDK = 21
- 虚拟线程启用:Spring Boot 项目加
spring.threads.virtual.enabled=true,先关做对比再开
回滚预案:
- 保留 JDK 17 镜像 tag 至少 1 个月
- 启动参数加
-XX:+UseG1GC暂用 G1(如果发现分代 ZGC 有问题) - Spring Boot 项目
spring.threads.virtual.enabled=false关闭虚拟线程
§7 踩坑实录
- 虚拟线程里用
synchronized会 pin 平台线程 —— synchronized 块执行时虚拟线程不能被卸载到其他载体线程,相当于退化回平台线程性能。JEP 491(Java 24)通过 synchronized 替换为内部锁机制解决,Java 21 仍是 pin。坑:Netty 内部用了 synchronized 块,虚拟线程跑 Netty handler 性能反而差。对策:用ReentrantLock替换,加-Djdk.tracePinnedThreads=full看哪些代码被 pin。 - 字符串模板(JEP 459)是 Preview 2,API 可能变 —— Java 22 撤回了 JEP 459(虽然重命名为 JEP 465),Java 25 列表里也没有字符串模板正式版(spec §3 查证)。生产代码禁用 Preview API——预览 API 一个版本就可能改签名。
- Switch 模式匹配(Java 21 正式)+ 老代码不识别 —— 老代码用反射
getClass().getName()看 switch case 找不到模式匹配的"case Circle c"信息。坑:Spring WebFlux 早期版本反序列化 Record 异常;用 Spring Boot 3.2+ + Jackson 2.15+ 解决。 - 分代 ZGC 在小堆(< 32GB)反而比 G1 慢 —— 分代 ZGC 维护"分代"的开销在小堆不划算。判断标准:堆 > 32GB 或 P99 暂停 < 50ms 才用 ZGC,否则 G1。
- 虚拟线程不能用 Thread.interrupt() 取消(部分场景)—— 虚拟线程跑 I/O 阻塞时(如
InputStream.read())interrupt 行为跟平台线程略有不同。Spring Boot 3.2+ / Tomcat 10.1+ 适配虚拟线程后自然解决。 - Record Patterns 嵌套超过 3 层编译慢 —— 嵌套
case Foo(Bar(Baz b))编译器做 pattern analysis 时间长。生产建议:嵌套不超过 2-3 层,需要更深时改用临时变量。
工业实践补充
真实项目里的 Java 21 升级路径(云原生 / 高并发 / 大数据典型场景):
- 阶段 1(1-2 月)评估期:用
jdeprscan --release 21 MyApp.jar扫所有用 Preview API 的代码(JEP 459 字符串模板、JEP 440 Record Patterns 在 Java 21 是 Preview)。Preview API 一个版本可能改签名或撤回,生产禁用。虚拟线程兼容性扫描:搜索代码里synchronized关键字,标注"虚拟线程友好" vs “需要替换 ReentrantLock”。 - 阶段 2(2-3 月)升级期:Maven/Gradle 目标切 JDK 21;JUnit 测试套重点验证虚拟线程场景(web / RPC / DB 访问用 1 万并发跑通);用
-Djdk.tracePinnedThreads=full记录被 pin 的位置,逐个改用 ReentrantLock。 - 阶段 3(1-2 月)切流期:单实例跑 1 周,监控虚拟线程性能(用 JFR
jdk.VirtualThreadPinned/jdk.VirtualThreadStartevents)+ GC 日志(-Xlog:gc*:file=gc.log)对比 Java 17 表现。关键指标:虚拟线程平均执行时间、pin 比例、GC 暂停时间分布。 - 阶段 4(全量):全量切。保留 JDK 17 镜像至少 1 个月,回滚窗口 < 5 分钟。
Java 21 性能数据(OpenJDK 官方 benchmark + 业内真实数据):
- 虚拟线程:高并发 I/O 场景(如 HTTP 客户端、gRPC、DB 查询)5-30 倍加速(见 §5 Demo 1 实测 31 倍);CPU 密集场景跟平台线程持平(虚拟线程不加速纯计算)。
- 分代 ZGC vs G1:16GB 堆下 ZGC 平均暂停 < 1ms,G1 平均暂停 50-200ms;但 ZGC 吞吐比 G1 低 5-10%。ZGC 是大堆 + 极低延迟专用。
- Switch 模式匹配 vs 传统
instanceof链:性能持平(编译器优化到相同字节码),开发体验大幅提升。 - Record Patterns 嵌套解构:编译器对 2-3 层嵌套做 pattern analysis 后生成最优字节码,性能跟手工解构持平;超过 3 层编译慢 30-50%。
- 序列集合
reversed():零拷贝视图(不复制数据),比老的new ArrayList<>(oldList).reverse()快 10-100 倍。
Spring Boot 3.2 + Java 21 迁移要点:
- Spring Boot 3.2 GA(2023-11):默认支持 Java 21(最低要求 Java 17)
- Spring Boot 3.2
spring.threads.virtual.enabled=true开启虚拟线程(Tomcat 默认线程池切到虚拟线程) - Spring Boot 3.2 + Spring Framework 6.1:MVC 控制器自动跑在虚拟线程上(无 synchronized pin)
- 注意:如果你用 Netty / Reactor / RxJava 响应式栈,虚拟线程对响应式性能提升有限(响应式本身已在做异步)
- 生产建议:CRUD 应用 / 微服务 / 阻塞 IO 应用直接升级 + 开虚拟线程;响应式应用保持现状
- 升级路径:Spring Boot 2.7.x → Spring Boot 3.0.x(要求 Java 17)→ Spring Boot 3.2.x(要求 Java 17,可选 Java 21)
虚拟线程生产调优清单:
- 看 pin 警告:用
-Djdk.tracePinnedThreads=full启动,看哪些 synchronized 块被频繁 pin - 替换 synchronized 为 ReentrantLock:Netty handler / 业务锁场景优先改
- JFR 分析:用
jdk.VirtualThreadStart/jdk.VirtualThreadPinned/jdk.VirtualThreadSubmitFailedevents 看虚拟线程健康度 - 载体线程数:默认
AvailableParallelism个载体线程(CPU 核心数),一般无需调整 - 堆大小:虚拟线程栈 KB 级,但任务对象仍在堆上,堆大小按业务对象 + 任务数 × 对象大小估算
- 超时取消:用
executor.submit(...).get(timeout, TimeUnit.SECONDS)强制超时,避免单任务卡住整批
§8 下一篇预告
下一站 Java 25(2025-09-16 发布)—— 分代 ZGC 成为默认 GC(JEP 474,从 JDK 23 起)+ Scoped Values 正式(JEP 506)+ 结构化并发(JEP 505)+ Vector API(JEP 508)+ Generational Shenandoah(JEP 521)。Java 25 是面向云原生 / 大数据 / AI 场景的现代 LTS。详见 Java 25 LTS 深度解读。
