JDK 21:JVM 与运行时更新#
本页讲 「工具与运行时」 桶。并发新特性见 虚拟线程。
运行时主线#
JDK 21 的 JVM 头条是 分代 ZGC(JEP 439):ZGC 引入年轻代/老年代,把"大部分对象朝生夕死"的事实利用起来,显著降低分配停顿与回收开销。同时 虚拟线程(JEP 444)作为运行时调度模型的大改,也带来运行时层面的新关注点(载体线程、pinning)。
分代 ZGC#
为什么需要#
此前的 ZGC 虽停顿极低,但非分代——每次都扫全堆,开销随堆变大而上升。现实中"新生对象很快变垃圾"是普遍规律(弱分代假说)。分代 ZGC 引入年轻代,频繁回收小而多垃圾的年轻代,老年代回收频率大幅降低,从而在保持低停顿的同时降低开销、提升吞吐。
怎么用#
JDK 21 中分代 ZGC 需显式开启(非分代仍是默认):
java -XX:+UseZGC -XX:+ZGenerational -Xmx16g -jar app.jar
下面是 examples/generational-zgc/ 里真实可运行的演示(snippets 嵌入,零漂移):
package com.javamodern.genzgc;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.stream.Collectors;
/**
* 演示分代 ZGC(JEP 439,JDK 21)。
*
* <p>非分代 ZGC 只有单个堆,全堆扫描成本随堆变大而上升;
* 分代 ZGC 把堆分为年轻代/老年代,频繁回收年轻代,显著降低分配停顿。
* MXBean 相应出现 {@code "ZGC Minor *"}(年轻代)与 {@code "ZGC Major *"}(老年代)。
* 测试 JVM 必须以 {@code -XX:+UseZGC -XX:+ZGenerational} 启动(见 {@code pom.xml})。
*/
public class GenerationalZgcDemo {
/** 当前所有 GC MXBean 的名字。 */
public static List<String> gcNames() {
return ManagementFactory.getGarbageCollectorMXBeans().stream()
.map(GarbageCollectorMXBean::getName)
.collect(Collectors.toList());
}
/** 是否为分代 ZGC:同时存在年轻代(Minor)与老年代(Major)bean。 */
public static boolean isGenerational() { // (1)
return gcNames().stream().anyMatch(n -> n.contains("Minor"))
&& gcNames().stream().anyMatch(n -> n.contains("Major"));
}
public static void main(String[] args) {
System.out.println("GC beans = " + gcNames());
System.out.println("isGenerational = " + isGenerational());
}
}
- :material-lightbulb: 分代的判定依据是 GC bean 名同时出现
Minor(年轻代)与Major(老年代)——这正是分代结构在 MXBean 上的投影。
运行 GenerationalZgcDemo.main 的实测输出(JDK 21 + 分代 ZGC):
GC beans = [ZGC Minor Cycles, ZGC Minor Pauses, ZGC Major Cycles, ZGC Major Pauses]
isGenerational = true
对照非分代 ZGC(仅
-XX:+UseZGC)的 bean 是[ZGC Cycles, ZGC Pauses]——没有 Minor/Major 之分。
底层原理#
graph TD
H["ZGC 堆"] --> Y["年轻代<br/>频繁、短周期回收(Minor)"]
H --> O["老年代<br/>低频、整堆回收(Major)"]
A["新对象"] --> Y
Y -->|存活多轮晋升| O
Y -->|Minor 回收(并发,极短停顿)| F["释放大量朝生夕死对象"]
O -.Major 回收(并发).- F
style H fill:#bbdefb
style Y fill:#c8e6c9
style O fill:#ffe0b2
虚拟线程的运行时视角#
虚拟线程(详见 虚拟线程)改变了线程调度模型,运行时层面有两点值得知道:
- 载体线程:虚拟线程是用户态的,挂载到少量载体平台线程(
ForkJoinPool)上,阻塞时卸载、让载体跑别的虚拟线程(M:N 调度)。 - pinning(钉住):JDK 21 中,
synchronized块内阻塞会钉住载体线程(无法卸载),削弱虚拟线程的伸缩性。IO 密集路径应用java.util.concurrent.locks.ReentrantLock替代synchronized。
其他运行时更新#
| 特性 | JEP | 说明 |
|---|---|---|
| 动态加载 Agent 预警 | 451 | 动态把 Java agent 加载进运行中的 JVM 会发出警告(为未来默认禁用铺路,"integrity by default");APM/监控类工具需关注 |
注:分代 ZGC 在 JDK 21 是可选的(非分代仍是默认,需
-XX:+ZGenerational显式开启);后续版本才转为默认。
与 JDK 8 / 17 对比#
java -jar app.jar # 默认 Parallel,Full GC 长停顿
java -XX:+UseZGC -Xmx16g -jar app.jar # 低停顿,但每次扫全堆
java -XX:+UseZGC -XX:+ZGenerational -Xmx16g -jar app.jar # 分代:频繁回收年轻代
常见坑 / 最佳实践#
- JDK 21 要手动开分代:漏掉
-XX:+ZGenerational得到的是非分代 ZGC(仍可用,但开销更高)。后续版本会默认分代。 - 分代不改变停顿量级:它降的是开销/吞吐,停顿本就 <1ms;选分代主要为了大堆下更经济。
- 虚拟线程 + synchronized:高并发虚拟线程场景注意 pinning——这是 JDK 21 运行时层面的典型坑(详见 虚拟线程「常见坑」)。
- GC 仍看发行版:要 Shenandoah/分代 Shenandoah 需对应发行版构建。
小结#
JDK 21 的 JVM 更新主线是「ZGC 走向分代 + 并发模型大改」:分代 ZGC 让低延迟 GC 更经济实用,虚拟线程让并发编程模型发生根本性变化。两者叠加,使 JDK 21 成为自 8 以来 JVM 吞吐/延迟/并发能力跨度最大的一个 LTS。