1710 字
约 5 分钟
6
S04-A03 · Java 8/17/21 新特性与团队基线补编

适用读者:要做"升不升 JDK"决策的人;要答"Java 17 有什么新东西"的面试者。
前置:S04-A01(版本演进与团队基线)、J11。
覆盖旧文:2769(Java 8)+3013、2770(Java 17)+3014、2771(Java 21)+3015 → 6 篇 / 约 4.3 万字符。
扩展点:E-S04-036(版本特性矩阵:8/11/17/21 的关键差异)、E-S04-037(升级收益/代价决策表)、E-S04-038(虚拟线程的适用边界)。
上一篇:S04-J11 · 反射、注解、泛型、序列化:运行时的四把钥匙
下一篇:S04-X02 · 技巧、错误总结与片段登记。


1. 一句话结论

升级 JDK 不是一个技术决定,是一个"收益 vs 迁移成本"的财务决定。 收益侧是 GC/启动/语法;成本侧是依赖兼容、--add-opens、废弃 API、测试回归。没有任何收益适合"顺手升"。

出处:A-2769–A-2771、A-3013–A-3015。


2. 版本特性矩阵(E-S04-036)

版本 LTS 语言 库/平台 运行时
8 ✅ Lambda、Stream、Optional、default 方法 新 java.time 元空间取代永久代
9 — (无大语法) 模块系统 JPMS、集合工厂 List.of G1 成默认
10 — var 局部变量推断 — —
11 ✅ var in lambda 参数 String.isBlank/strip/repeat、Files.readString、HTTP Client 标准化 ZGC 实验性、G1 优化
14 — switch 表达式(预览)、record(预览) — CMS 移除
15 — 文本块 — 偏向锁默认禁用
16 — record 正式、instanceof 模式匹配 — —
17 ✅ sealed 类、switch 模式匹配(预览) 强封装 JDK 内部(--add-opens 需求爆发) ZGC 生产可用
21 ✅ switch 模式匹配正式、record 模式、虚拟线程、SequencedCollection 分代 ZGC 虚拟线程(JEP 444)

必答三点:

  1. var 只是语法糖(编译期推断,不是动态类型),不能用于字段/方法参数/返回值。
  2. record 是"透明数据载体":自动 equals/hashCode/toString,字段 final,不能继承类(可实现接口)。
  3. sealed 让"穷尽性检查"成为可能:sealed interface Shape permits Circle, Square + switch 模式匹配编译器可校验分支完整。

3. 升级决策表(E-S04-037)

决策维度 8 → 11 11 → 17 17 → 21
主要收益 HTTP Client、String/Files 便利方法、G1 改进 强封装(更安全)、record/sealed、ZGC 可用 虚拟线程(IO 密集吞吐)、分代 ZGC(低停顿)
主要成本 少数依赖不兼容、Jigsaw 前期踩坑 --add-opens 大面积出现(反射库、序列化库) 虚拟线程需改造(synchronized pinning)
风险点 反射内部 API 报警告 框架版本必须同步升(Spring Boot 3 需 17+) 线程池模型与虚拟线程混用的调试复杂度
不升的理由 生态未验证、客户环境锁定 依赖链太长、缺少回归测试 现有吞吐已达标、无低延迟诉求

判据(架构师口径):升级的最小充分条件 = "有明确指标需要它"——例如"GC 停顿 P99 > 500 ms"→ 考虑 G1/ZGC;"IO 密集服务线程数打满"→ 考虑虚拟线程。没有指标,不升级。


4. 虚拟线程的适用边界(E-S04-038)

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i ->
        executor.submit(() -> { Thread.sleep(1000); return i; }));
}   // JDK 21;约 1 秒完成 1 万任务(IO 等待被复用)
维度 平台线程 虚拟线程(21)
栈 固定 1 MB(默认) 按需增长,KB 级
调度 内核 1:1 JVM 调度到载体线程(ForkJoinPool)
阻塞 占内核线程 IO/sleep 卸载,不占载体
pinning — synchronized 块内阻塞会钉住载体线程(21 限制)
适合 CPU 密集 IO 密集、高并发短任务

三条边界(必答):

① CPU 密集任务不会因虚拟线程变快(并行度 = 核数)
② synchronized 内做阻塞 IO 会 pin 载体线程 → 用 ReentrantLock 替代
③ 不要池化虚拟线程(它本就轻量,池化反而失去意义)

待核:JDK 24 起对 synchronized pinning 有改进(JEP 491),本文按 JDK 21 口径写。


5. 迁移执行清单

阶段 动作 验收
1 评估 依赖树扫描(jdeps/mvn dependency:tree),列出反射内部 API 使用点 生成 --add-opens 候选清单
2 构建 升级编译插件 target/release,开 -Xlint:deprecation 编译零警告(或登记豁免)
3 运行 灰度节点跑新 JDK,对比 P99、GC 停顿、错误率 三项指标不劣化
4 回滚 保留旧 JDK 镜像与启动脚本 5 分钟内可回切
5 固化 CI 固定 JDK 版本(toolchain),写进 README 新成员环境一致
# 快速定位内部 API 依赖(JDK 17+)
jdeps --jdk-internals -R app.jar

6. 反例:五种"升级踩坑"的典型

做法 后果
只升运行时、不升框架 Spring Boot 2.x 在 17 上能跑但不受支持;3.x 强制 17+
全量 --add-opens java.base 等于放弃强封装收益,且掩盖真实依赖问题
把虚拟线程塞进固定线程池 失去调度优势,还可能造成 pinning 放大
用 var 替代所有类型 可读性下降,代码评审成本上升
升级后不改 CI JDK 本地 21、CI 8,产物在线上行为不同

7. 自测

  1. var 与动态类型的区别?哪些位置不能用?
  2. record 的三个自动方法与两条限制?
  3. sealed 解决的问题是什么?
  4. 虚拟线程的三条边界?为什么不能池化?
  5. 升级决策的最小充分条件?迁移五阶段?
答案
  1. 编译期推断的静态类型;字段/参数/返回值/局部未初始化不可用。
  2. equals/hashCode/toString;字段 final 且不能继承类(可实现接口)。
  3. 让类型集合封闭,编译器可做穷尽性检查(配合 switch 模式匹配)。
  4. CPU 密集无收益、synchronized 阻塞 pin 载体、不要池化;池化失去轻量意义。
  5. 有明确指标需要它;评估→构建→灰度运行→回滚→固化。

8. 下一篇

S04-X02 · 技巧、错误总结与片段登记 —— 正则、日志与调试、单元测试、设计模式、关键词大全等 9 篇的登记表与去向。


本篇属于系列《S04 · Java 语言核心》。旧文来源:A-2769/2770/2771、A-3013/3014/3015;扩展点 E-S04-036/037/038。

S04-A03 · Java 8/17/21 新特性与团队基线补编
https://www.clxhxhhr.top/posts/5238/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。