跳转至

JDK 21:JVM 与运行时更新#

版本信息

  • JDK:21(LTS,2023-09 GA)
  • 本页覆盖的关键 JVM JEP439(分代 ZGC)、 444(虚拟线程)、 451(动态加载 Agent 预警)

本页讲 「工具与运行时」 桶。并发新特性见 虚拟线程

运行时主线#

JDK 21 的 JVM 头条是 分代 ZGCJEP 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());
    }
}
  1. :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。

参考#