Skip to content
Charles Shao
Go back

JDK 版本差异精讲:从 8 到 25,LTS 演进与关键能力

views

前三篇把 JVM 内存与类加载垃圾回收JIT 与调优讲透了。落到工程上还有一个绕不开的问题:线上钉在哪个 JDK?从 8 升到 17 / 21 / 25,语言、运行时和升级成本到底变了什么?

本篇不以完整 JEP 清单铺开,而以 LTS(Long-Term Support) 为主线,把真正影响写法、默认行为与迁移成本的关键差异讲清楚,并给出可落地的选型建议。

TL;DR

Table of contents

Open Table of contents

1. 先搞清:什么是 LTS

JDK 9 之后,Oracle / OpenJDK 采用 每 6 个月发一版 的节奏。其中少数版本标为 LTS:厂商提供更长周期的安全更新与补丁,生态(Spring、中间件、云镜像、监控 Agent)也优先对齐这些版本。

角色版本含义
LTS8、11、17、21、25…生产基线;支持周期以年计
非 LTS(Feature Release)9–10、12–16、18–20、22–24…功能试验田;通常只维护到下一版发布

一句话:非 LTS 是「功能快递」,LTS 是「可长期寄居的房子」。团队缺专人跟版本演进时,钉 LTS 几乎总是更优解。

JDK LTS 时间线:8(2014)→ 11(2018)→ 17(2021)→ 21(2023)→ 25(2025)

自 21 起 LTS 约两年一档;图中箭头标注相邻 LTS 的大致间隔。

各 LTS 代表性能力一览:语言/API(绿)、运行时(蓝)、破坏性变更(粉)

绿 = 语言与标准库;蓝 = 运行时 / 性能;粉 = 移除或强约束 —— 升级时粉条最容易踩坑。

关键变化哪版一句话
PermGen → Metaspace8类元数据出堆、用本地内存,缓解 PermGen space OOM
默认 GC9+Parallel → G1;CMS 在 14 删除
模块系统 JPMS9 / 11 LTSJDK 自身模块化;应用可不写 module-info,但内部 API 渐难反射
Lambda / Stream8行为参数化 + 声明式集合流水线
Records / Sealed / Pattern Matching16–17数据载体、限制继承、instanceof 模式绑定
虚拟线程21JVM 调度的轻量线程;I/O 密集高并发的现代答案
Scoped Values25 正式虚拟线程时代替代部分 ThreadLocal 的作用域共享

2. 运行时关键差异:Metaspace、G1、日志

语言特性看得见,默认 GC、方法区实现、日志开关却常在升级后才以事故形式出现。

运行时默认值变迁:永久代→元空间、Parallel→G1、PrintGC→Xlog、ZGC→分代 ZGC/Shenandoah

2.1 PermGen → Metaspace(JDK 8)

永久代(≤7)元空间(8+)
存什么类元信息、方法、常量池等同类信息(字符串常量池 JDK 7 已先挪进堆)
内存位置JVM 堆内一块固定/难扩的区域本地内存(Native),默认可按需扩
典型故障java.lang.OutOfMemoryError: PermGen spaceMetaspace(常因动态类太多:反射、CGLIB、热部署)
调参-XX:PermSize / MaxPermSize-XX:MetaspaceSize / MaxMetaspaceSize

换的原因:永久代大小难估,加载类一多就 OOM;元空间用本地内存,默认更弹性。但不是「永远不 OOM」——动态生成类失控照样打满 Metaspace。对象仍在堆,类的「图纸」在元空间,对应内存篇的方法区实现变迁。

2.2 默认 GC:Parallel → G1,CMS 退场

2.3 GC 日志:-XX:+PrintGCDetails-Xlog:gc*

旧旗标(PrintGCDetailsPrintGCDateStamps 等)在统一日志后逐步失效或被替代。新写法:-Xlog:gc*(可再加 gc+heapgc+age 等标签)。统一日志是 整套 JVM 日志框架,不只 GC。


3. 语言与 API:从 8 到 17

3.1 JDK 8:Lambda、Stream、接口默认方法

顺带一提集合实现:JDK 8 起 HashMap 在链表过长(默认树化阈值 8,且容量 ≥64)时转为 红黑树,最坏查找从 O(n) 降到 O(log n);扩容时由头插改为尾插,降低并发场景下成环风险(HashMap 本身仍非线程安全,并发用 ConcurrentHashMap)。

3.2 JDK 11:var、HttpClient、模块化落地

模块系统(JPMS,9 引入,11 作为 LTS 广泛落地)

  1. JDK 自身拆成模块,加强封装,减少「随便反射进 sun.*」。
  2. 应用 可以继续用 classpath(未模块化),但会越来越常碰到 非法反射访问 警告 / 错误。
  3. 关键词:module-info.javarequires / exports、模块路径 vs 类路径。
  4. 升级痛点:JAXB 等 Java EE / CORBA 模块从 JDK 移除,要用就显式加依赖;sun.misc.BASE64Encoder 之类内部 API 不可再依赖。

3.3 JDK 17:Records、Sealed、Pattern Matching、Text Blocks

17 把 12–16 预览期沉淀的一批语言特性收成正式能力,也是 Spring Boot 3 等生态对齐的基线(要求 17+)。

// Records:不可变数据载体,自动 equals/hashCode/toString/accessor
record Point(int x, int y) {}

// Pattern Matching for instanceof(16 正式,17 生态标配)
if (obj instanceof String s) {
    System.out.println(s.toLowerCase());
}

// Sealed:限制谁可以继承,配合 switch 做穷尽检查
sealed interface Shape permits Circle, Rect {}
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}

运行时侧:G1 默认地位稳固;强封装 JDK 内部 API 默认拒绝许多非法反射 —— 过去靠 --add-opens 苟着的库,到 17 往往会彻底暴露问题。


4. 并发分水岭:虚拟线程(JDK 21)

21 是近年来对服务端架构影响最大的 LTS —— 关键词是 Virtual Threads(虚拟线程,Project Loom)

平台线程(Platform Thread)虚拟线程(Virtual Thread)
调度OS 调度JVM 调度
成本栈大约 MB 级,几千到上万就吃紧很轻,可到 百万级 并发任务
模型线程池精心限流可「一任务一线程」重新成立
擅长CPU 密集仍靠它阻塞 I/O 密集(吞吐型并发)

机制三词

  1. Carrier(载体线程):虚拟线程跑在少量平台线程上。
  2. Mount / Unmount:遇到阻塞 I/O,虚拟线程可从 carrier 卸下,carrier 去跑别的;I/O 完成后再挂上去。
  3. Pinning(钉住):若在虚拟线程里持有 synchronized 监视器 或进入某些 native 帧,可能无法 unmount,carrier 被钉住 —— 吞吐回落。优先用 ReentrantLock 等,并避免在虚拟线程里堆重 CPU 或大线程局部状态。JDK 21–23 上可用 -Djdk.tracePinnedThreads / JFR VirtualThreadPinned 观察;JDK 24(JEP 491)synchronized 内阻塞不再因此 pinning。
// JDK 21:按任务创建虚拟线程 —— 适合大量阻塞调用
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i ->
        executor.submit(() -> httpClient.send(request, BodyHandlers.ofString())));
}

虚拟线程让你用 同步写法 接近异步的伸缩性;不是取代 Netty 这类网络栈,而是减少「手写异步回调」的必要。CPU 密集仍要控制并行度,别幻想「开一百万虚拟线程算矩阵就更快」。它也 不会一刀切取代线程池:I/O 密集、高并发任务可用虚拟线程简化模型;需要严格限流、或与线程绑定昂贵资源时,平台线程池仍然合适。

21 同期值得记住的


5. JDK 25:当前最新 LTS

25 于 2025-09-16 GA,是 21 之后的 LTS。相对 21,更突出 运行时效率、AOT 易用性、可观测性,以及若干 API 收官。

能力含义
Scoped Values(正式)作用域内只读共享,虚拟线程下比乱用不限的 ThreadLocal 更安全、语义更清晰
Compact Object Headers压缩对象头,降低堆占用 —— 扣回内存篇对象头知识
分代 Shenandoah低延迟 GC 阵营与分代 ZGC 并列的可选项
AOT / Leyden 易用性改善启动与预热(对照 JIT 预热痛点)
Preview / IncubatorStructured Concurrency、Primitive Patterns、Vector API 等 尚未稳定,核心路径别赌

定位:新项目优先 21(生态最成熟的「现代 Java」);已在 21 且依赖支持,再评估 25。不要把 Preview 特性写进核心契约。


6. 升级路径与选型

生产选型路径:离开 8 → 11/17 存量 → 21 新项目首选 → 可选 25

稳字当头:先离开失控的旧基线,再按生态成熟度跃进。

现状建议
仍在 8尽快迁出;优先目标 1721(视 Spring 等框架约束)
停在 11规划升 17/21;重点处理模块封装与 EE 依赖
已在 17 且稳定可守;新服务用 21,存量择机跳
新项目默认 21;团队愿跟最新 LTS 且依赖支持则评估 25
低延迟交易类在 21+ 评估 分代 ZGC;25 可看 分代 Shenandoah 与对象头压缩收益

跨版本升级的最小闭环

  1. 依赖扫描jdeps、构建插件的 bytecode 基线检查、第三方组件官方支持矩阵。
  2. 编译到目标 --release,打开警告,先消灭非法访问与已移除 API。
  3. 同负载压测:对比 P99、GC 停顿、CPU、RSS;GC 日志格式变了,看板也要改。
  4. 灰度:先非核心实例,再按错误预算放量。

头号拦路虎:非法反射 + 已移除 API + 过时 GC / 日志参数

通用在线服务用 G1 通常够用;堆很大且要 稳定低停顿 再上 ZGC(21+ 优先分代 ZGC)。没有绝对更快,只有停顿 / 吞吐 / 内存的取舍(铁三角见 GC 篇)。


7. 和本系列前几篇怎么对上号

理论篇知识点版本演进里对应的变化
方法区 / 对象头Metaspace(8);Compact Object Headers(25)
G1 / ZGC / 分代9+ G1 默认;21 分代 ZGC;25 分代 Shenandoah
JIT 预热Leyden / AOT 相关能力持续改善启动
线程与栈成本21 虚拟线程;25 Scoped Values 正式

理论是机制,版本是机制在时间轴上的落点 —— 两者叠在一起,选型与调参才不会凭感觉。

监控与排障可继续看 Grafana JVM 监控Arthas 诊断

延伸阅读


views
Share this post on:

Previous Post
Grafana JVM 监控分析:从指标采集到看板解读的实战指南
Next Post
JVM 深入(三)· 进阶篇:JIT、内存模型与调优前沿