概述
JDK 17 是 Java 的长期支持版本(LTS),于 2021 年 9 月发布。上一个 LTS 是 JDK 11(2018 年 9 月);中间 JDK 12~16 为半年一发的短期特性版本,每版引入少量 JEP,最终在 JDK 17 汇总为新的 LTS 基线。
从 JDK 11 直接跳到 JDK 17 时,需要关注的不只是 JDK 17 当版的 14 个 JEP,还包括 12~16 中已转正的语言特性(switch 表达式、文本块、record、模式匹配等)以及 JVM、工具链方面的累积变化。版本节奏与 LTS 策略见 Java 版本历史。
版本路线(JDK 11 → JDK 17)
| 版本 | 发布日期 | 类型 | JEP 数 |
|---|---|---|---|
| JDK 11 | 2018-09 | LTS | 17 |
| JDK 12 | 2019-03 | 短期 | 8 |
| JDK 13 | 2019-09 | 短期 | 5 |
| JDK 14 | 2020-03 | 短期 | 16 |
| JDK 15 | 2020-09 | 短期 | 14 |
| JDK 16 | 2021-03 | 短期 | 17 |
| JDK 17 | 2021-09 | LTS | 14 |
JDK 12(2019-03)
| JEP | 特性 | 说明 |
|---|---|---|
| 189 | Shenandoah GC(实验) | 低延迟 GC,首次以实验特性引入 |
| 230 | 微基准测试套件 | JMH 纳入 JDK,便于性能基准编写 |
| 325 | switch 表达式(预览) |
switch 可作表达式返回值,支持 yield |
| 334 | JVM 常量 API | java.lang.constant 包,描述 nominal 常量 |
| 340 | 统一 AArch64 端口 | 合并 32/64 位 ARM 端口实现 |
| 341 | 默认 CDS 归档 | 安装时生成默认 class 数据共享归档,缩短启动时间 |
| 344 | G1 可中止混合回收 | 混合 GC 周期过长时可中止,降低停顿风险 |
| 346 | G1 及时归还空闲内存 | 空闲堆内存更快归还给操作系统 |
JDK 13(2019-09)
| JEP | 特性 | 说明 |
|---|---|---|
| 350 | 动态 CDS 归档 | 应用退出时生成定制 CDS 归档,进一步加速启动 |
| 351 | ZGC 归还未用内存 | ZGC 将未使用堆页归还给 OS |
| 353 | 重写旧版 Socket API | Socket/ServerSocket 底层实现现代化 |
| 354 | switch 表达式(第二次预览) |
语法与行为微调 |
| 355 | 文本块(预览) | 多行字符串字面量,三引号 """ 语法 |
JDK 14(2020-03)
语言特性
| JEP | 特性 | 说明 |
|---|---|---|
| 305 | instanceof 模式匹配(预览) |
在 instanceof 中直接绑定变量 |
| 361 | switch 表达式(正式) |
预览两轮后转正,可替代传统 switch 语句 |
| 359 | record(预览) |
不可变数据载体,紧凑语法 |
| 368 | 文本块(第二次预览) | 转义规则与缩进处理改进 |
switch 表达式示例:
String dayType = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "周末";
default -> throw new IllegalArgumentException("无效日期");
};
instanceof 模式匹配(预览阶段)示例:
if (obj instanceof String s) {
System.out.println(s.length());
}
JVM、平台与工具
| JEP | 特性 | 说明 |
|---|---|---|
| 343 | 打包工具(孵化) | jpackage 首次以孵化 API 引入 |
| 345 | G1 NUMA 感知分配 | 多插槽机器上优化 G1 内存布局 |
| 349 | JFR 事件流 | 持续消费 JFR 事件,无需先写文件 |
| 352 | 非易失性映射字节缓冲区 | 支持 NVRAM 上的 MappedByteBuffer |
| 358 | 更友好的 NPE 信息 | 空指针异常附带更精确的空引用位置 |
| 362 | 废弃 Solaris/SPARC 端口 | 后续在 JDK 15 移除 |
| 363 | 移除 CMS 收集器 | Concurrent Mark Sweep GC 正式删除 |
| 364 | ZGC 支持 macOS | |
| 365 | ZGC 支持 Windows | |
| 366 | 废弃 Parallel Scavenge + Serial Old 组合 | |
| 367 | 移除 Pack200 | 工具与 API 一并删除 |
| 370 | 外部内存访问 API(孵化) | Panama 项目,JNI 替代方向 |
JDK 15(2020-09)
语言特性
| JEP | 特性 | 说明 |
|---|---|---|
| 360 | 密封类(预览) | sealed/permits 限制继承层次 |
| 375 | instanceof 模式匹配(第二次预览) |
|
| 378 | 文本块(正式) | 多行字符串定稿 |
| 384 | record(第二次预览) |
文本块示例:
String json = """
{
"name": "Java",
"version": 17
}
""";
JVM、平台与工具
| JEP | 特性 | 说明 |
|---|---|---|
| 339 | EdDSA 签名算法 | Signature 支持 Edwards 曲线 |
| 371 | 隐藏类 | 框架动态生成、不可发现的类 |
| 372 | 移除 Nashorn | JDK 内置 JavaScript 引擎删除 |
| 373 | 重写 DatagramSocket API | 与 Socket 重写(JEP 353)配套 |
| 374 | 禁用并废弃偏向锁 | 现代多线程负载下收益有限 |
| 377 | ZGC 生产就绪 | 低延迟 GC 脱离实验状态 |
| 379 | Shenandoah 生产就绪 | 与 JDK 12 实验引入对应 |
| 381 | 移除 Solaris/SPARC 端口 | |
| 383 | 外部内存访问 API(第二次孵化) | |
| 385 | 废弃 RMI Activation | JDK 17 移除(JEP 407) |
JDK 16(2021-03)
语言特性
| JEP | 特性 | 说明 |
|---|---|---|
| 394 | instanceof 模式匹配(正式) |
预览两轮后转正 |
| 395 | record(正式) |
不可变数据类定稿 |
| 397 | 密封类(第二次预览) |
record 示例:
public record Point(int x, int y) {}
Point p = new Point(1, 2);
System.out.println(p.x()); // 1
JVM、平台与工具
| JEP | 特性 | 说明 |
|---|---|---|
| 338 | 向量 API(孵化) | SIMD 向量计算 |
| 347 | 启用 C++14 | HotSpot 源码构建要求 |
| 357 | 迁移到 Git | 版本控制从 Mercurial 切换 |
| 369 | 迁移到 GitHub | OpenJDK 托管平台变更 |
| 376 | ZGC 并发线程栈处理 | 降低 ZGC 停顿 |
| 380 | Unix 域套接字通道 | SocketChannel 支持 AF_UNIX |
| 386 | Alpine Linux 端口 | 容器镜像常用发行版 |
| 387 | Elastic Metaspace | 元空间按需伸缩、及时归还 |
| 388 | Windows/AArch64 端口 | |
| 389 | 外部函数链接器 API(孵化) | Panama,调用原生函数 |
| 390 | 值类型类警告 | 对 Integer 等误用同步发出警告 |
| 392 | 打包工具(正式) | jpackage 转正 |
| 393 | 外部内存访问 API(第三次孵化) | |
| 396 | 默认强封装 JDK 内部 API | sun.* 等默认不可访问;JDK 17 由 JEP 403 最终定稿 |
JDK 17(2021-09,LTS)
语言特性
| JEP | 特性 | 说明 |
|---|---|---|
| 409 | 密封类(正式) | 预览两轮后转正 |
| 406 | switch 模式匹配(预览) |
在 switch 中对类型/值做模式匹配 |
密封类(JEP 409)
密封类和密封接口用来限制哪些类可以继承或实现某个类/接口。
Java 原有两个极端:final 类完全不能继承,普通类/接口任何人都能继承,缺少「只允许指定的几个子类」这个中间选项。密封类主要解决:
1. 模式匹配的穷举性检查
没有密封类时,switch 无法知道所有子类,必须写 default,漏掉某种情况编译器不报错:
// 旧写法:漏掉 Triangle 也不报错
String desc = switch (shape) {
case Circle c -> "圆";
case Rectangle r -> "矩形";
default -> "未知"; // 掩盖了遗漏
};
// 密封类:漏掉 Triangle 编译器直接报错
String desc = switch (shape) {
case Circle c -> "圆";
case Rectangle r -> "矩形";
// 编译错误:缺少 Triangle
};
2. 表达领域约束
某些业务类型天然有固定变体,密封类可以在类型系统层面强制表达这种约束,而不只是靠文档约定。
3. 比枚举更强
枚举每个值只是单例,无法携带不同的字段;密封类每种子类可以有完全不同的结构:
sealed interface PaymentResult permits Success, Failure, Pending {}
record Success(String transactionId, BigDecimal amount) implements PaymentResult {}
record Failure(String errorCode, String message) implements PaymentResult {}
record Pending(String trackingId) implements PaymentResult {}
核心关键字:
sealed— 声明该类/接口是密封的permits— 列出允许继承的子类- 子类必须选择三者之一:
final— 不能再被继承sealed— 继续密封,再限制下一级non-sealed— 开放,任何人都可以继承
public sealed interface Shape
permits Circle, Rectangle, Triangle {}
public final class Circle implements Shape {
double radius;
}
public final class Rectangle implements Shape {
double width, height;
}
public non-sealed class Triangle implements Shape {
double base, height;
}
配合 switch 表达式,编译器知道所有可能的子类型,可做完整性检查:
double area = switch (shape) {
case Circle c -> Math.PI * c.radius * c.radius;
case Rectangle r -> r.width * r.height;
case Triangle t -> 0.5 * t.base * t.height;
};
JVM、平台与工具
| JEP | 特性 | 说明 |
|---|---|---|
| 306 | 恢复始终严格浮点语义 | 浮点运算恢复一致严格模式,strictfp 语义简化 |
| 356 | 增强型伪随机数生成器 | 新增 RandomGenerator 接口及多种算法实现 |
| 382 | macOS Metal 渲染管道 | Java 2D 在 macOS 上用 Metal 替代 OpenGL |
| 391 | macOS/AArch64 移植 | 原生支持 Apple Silicon(M 系列芯片) |
| 398 | 废弃 Applet API | 标记 @Deprecated(forRemoval = true) |
| 403 | 强封装 JDK 内部 API | 延续 JEP 396,封闭 sun.* 等内部包 |
| 407 | 移除 RMI Activation | java.rmi.activation 包删除 |
| 410 | 移除实验性 AOT/JIT 编译器 | 基于 Graal 的实验编译器移除 |
| 411 | 废弃安全管理器 | SecurityManager 标记待移除 |
| 412 | 外部函数与内存 API(孵化) | Panama 第四次孵化,JNI 替代方向 |
| 414 | 向量 API(第二次孵化) | SIMD 向量计算持续演进 |
| 415 | 上下文反序列化过滤器 | 按上下文设置 Java 反序列化白名单 |
增强型随机数生成器示例:
RandomGenerator generator = RandomGeneratorFactory.of("L64X128MixRandom").create();
int randomInt = generator.nextInt(100);
外部函数与内存 API(孵化)示例:
try (MemorySegment segment = MemorySegment.allocateNative(100)) {
// operate on off-heap memory
}
语言特性演进小结(JDK 12 → 17)
| 特性 | JDK 12 | JDK 13 | JDK 14 | JDK 15 | JDK 16 | JDK 17 |
|---|---|---|---|---|---|---|
switch 表达式 |
预览 | 预览 | 正式 | |||
| 文本块 | 预览 | 预览 | 正式 | |||
instanceof 模式匹配 |
预览 | 预览 | 正式 | |||
record |
预览 | 预览 | 正式 | |||
| 密封类 | 预览 | 预览 | 正式 | |||
switch 模式匹配 |
预览 |
从 JDK 11 升级到 JDK 17 时,上表中标为正式的特性均无需 --enable-preview 即可使用。
版本信息
- 发布日期:2021 年 9 月 14 日
- LTS 版本;上一个 LTS 为 JDK 11
- JDK 17 当版 JEP 数量:14 个
- Class 文件 major version:61
Oracle 官方支持周期
| 阶段 | 截止时间 | 说明 |
|---|---|---|
| Premier Support | 2026 年 9 月 | 全面支持:bug 修复、安全补丁、新功能更新、性能改进 |
| Extended Support | 2029 年 9 月 | 仅安全补丁和关键 bug 修复,不引入新功能,Oracle JDK 用户通常需额外付费 |
| Sustaining Support | 2029 年 9 月之后 | 无限期,仅提供已有补丁的访问,不再产出新补丁 |
从 JDK 17 升级到 JDK 21 的策略
JDK 17 的 Premier Support 于 2026 年 9 月到期,下一个 LTS 版本是 JDK 21。推荐采用三阶段迁移,而非一步到位:
阶段一:替换废弃 API(在 JDK 17 上完成)
- 用
jdeprscan --release 21扫描废弃 API 使用情况 - 替换
SecurityManager、finalize()、Thread.stop()等将被移除的 API - 检查所有未显式指定字符集的代码(如
new String(bytes)、new FileReader(file)),统一改为StandardCharsets.UTF_8——JDK 18 起默认字符集改为 UTF-8,这是最常见的隐性 breaking change
阶段二:升级第三方库(仍在 JDK 17 上运行)
找到同时兼容 JDK 17 和 JDK 21 的库版本作为过渡版本,逐库升级,不要一次全换:
- Spring Boot → 3.2+(同时支持 JDK 17 和 JDK 21)
- Mockito → 5.x
- ByteBuddy → 1.14+
- 其他字节码增强类框架同步升级
升级后部署上线,稳定运行一段时间再进行下一阶段。
阶段三:升级 JDK 到 21
代码和依赖都已验证稳定后,切换 JDK 版本,代码不动:
- 跑完整测试套件
- 上线,观察 GC 行为、线程池等运行指标
这样每个阶段都有独立的回滚点,出问题能快速定位是代码变更还是运行时升级引入的。
参考
维护记录
| 时间 | 修改内容 | 原因 |
|---|---|---|
| 2026-06-20 | title 改为「JDK 17 特性」 | 全系列统一命名,去掉「新特性」 |
| 2026-06-20 | 重命名为 jdk-17.md;title 改为「JDK 17」;url 改为 jdk-17;移至 language/java/ |
全系列统一简洁命名 |
| 2026-06-20 | 补充 JDK 12~17 全部 JEP;明确上一 LTS 为 JDK 11;增加语言特性演进表 | 文档应覆盖 11→17 升级路径上的累积变化 |