10分钟了解一下jdk21 的一些新特性

JDK21 核心新特性

一、重磅稳定正式特性(生产可直接使用)

1. JEP444 虚拟线程 Virtual Threads(最核心、变革并发编程)

底层原理

传统平台线程:1:1 绑定操作系统内核线程,每个线程栈默认 1MB,数量上限几千,线程池需要反复调参(核心线程、队列、拒绝策略)。 虚拟线程:JVM 自研 M:N 调度,由载体线程(Carrier Thread) 复用 OS 线程;虚拟线程栈极小(几百字节),可轻松创建百万级线程。 当虚拟线程发生 IO 阻塞(DB、Redis、HTTP、sleep),会自动卸载、归还载体线程给其他虚拟线程使用,不会占死 OS 线程。

复制代码
// 1. 快速创建单个虚拟线程
Thread.startVirtualThread(() -> System.out.println("虚拟线程任务"));

// 2. 生产推荐:每个任务单独分配虚拟线程(无固定线程池大小限制)
try (ExecutorService vtExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
    // 提交百万任务无OOM风险,传统ThreadPoolExecutor会直接打满队列触发拒绝策略
    for (int i = 0; i < 1_000_000; i++) {
        vtExecutor.submit(() -> {
            // 业务:调用Feign、查询数据库、抽帧任务等IO密集操作
        });
    }
} // try-with-resources自动等待全部任务完成,自动关闭

对业务的价值

  1. 不用手动配置 corePoolSize/maxPoolSize/queue,省去线程池调优成本;
  2. IO 密集型任务吞吐量大幅提升;
  3. 天然适配 "一请求一线程",不用异步回调、CompletableFuture 嵌套,代码同步写法更简单;

=================什么是 IO 密集型 / CPU 密集型=============================

  1. IO 密集型

程序大部分时间卡在等待外部资源,CPU 空闲没事干:

  • 网络 IO:RTSP 拉流、Feign 调工作流、HTTP 请求大模型、Redis、MySQL 查询
  • 文件 IO:FFmpeg 读写视频、MinIO 上传图片、本地文件读写

线程发起 IO 请求后,会阻塞等待返回,这期间 CPU 完全闲置。

2.CPU 密集型

大量复杂计算、循环、编解码运算,CPU 全程满载,几乎无等待。

比如:本地视频解码、超大报表计算、复杂数学运算。

传统平台线程池为什么不适合 IO 密集?

传统 Java 线程是操作系统内核线程 1:1 映射

  1. 每个线程占用固定栈内存(默认 1MB),操作系统能承载的线程上限很低,一般几千条顶天;

  2. 线程阻塞在 IO 时,内核线程会被占死,无法复用

举个例子: 你开 200 个线程池,200 个任务同时请求大模型接口,全部阻塞等待响应。 此时 200 个 OS 线程全部卡死,哪怕 CPU 空闲,也没有多余线程处理新的任务,吞吐量卡死。 想要提高并发,只能疯狂调大线程池 max,线程越多内存占用越高,容易 OOM。

JDK21 虚拟线程为什么对 IO 密集型极度友好?

核心原理:M:N 调度,IO 阻塞自动释放载体线程

  1. 虚拟线程本身极轻量,栈内存极小,支持创建百万级

  2. 虚拟线程阻塞在网络 / DB IO 时,JVM 会自动把它挂起,载体线程释放出来给其他虚拟线程使用; 载体线程只是少量固定的平台线程,不断复用。

为什么 CPU 密集型不适合虚拟线程?

如果是纯计算场景,虚拟线程不会阻塞,会一直占用载体线程疯狂运算,载体线程被钉死无法复用,此时虚拟线程没有任何优势,和普通线程池性能差别不大。

【前置知识】

载体线程(Carrier Thread)

本质就是普通平台线程(OS 内核线程),由 JVM 内部维护少量固定数量,相当于工人。

JDK 虚拟线程底层用 ForkJoinPool 作为载体线程池,默认载体数量 = CPU 核心数。

虚拟线程(Virtual Thread)

JVM 层面轻量化逻辑线程,不直接绑定 OS 线程,只是一段任务逻辑,本身不占用操作系统资源,可创建百万个。

核心关系:M:N 映射

N 个虚拟线程,复用 M 个载体线程(M 远小于 N)。

同一时刻,一个载体线程只能执行一个虚拟线程

大白话举例

载体线程 = 食堂厨师(只有 4 个)

虚拟线程 = 待处理的任务(几千上万个)

  • 任务 A 正在厨师 1 手上处理,中途要等外部 AI 接口返回(IO 阻塞);
  • 厨师 1 不用干等,放下任务 A(挂起虚拟线程),去处理任务 B;
  • 等 AI 接口数据回来,任务 A 再排队,有空厨师再接着处理。

传统线程对比: 传统平台线程执行 IO 阻塞,厨师会卡死在这个任务上,啥也干不了,只能新增更多厨师(线程)。

IO 完成后如何恢复?

当操作系统返回 IO 数据(网络 / 数据库响应):

  1. JVM 收到 IO 就绪通知;
  2. 该虚拟线程重新进入就绪队列;
  3. 载体线程空闲时,再次绑定它,从阻塞处继续往下执行剩余业务代码。

补充两个关键点

(1)什么时候载体线程不会释放?

如果使用 synchronized 同步块锁住代码,虚拟线程阻塞在锁竞争时,不会解绑载体线程,载体线程被占用无法复用,会丧失虚拟线程优势。

(2)CPU 密集场景无复用效果

如果虚拟线程全是纯计算,无任何 IO 阻塞,虚拟线程会一直占用载体线程跑计算,载体无法释放,此时虚拟线程没有性能提升。

========================================================================

2. JEP441 Switch 模式匹配(转正,简化类型判断)

告别 **instanceof + 强转**样板代码,支持类型匹配、null 匹配、when 条件判断。

旧写法:

复制代码
Object obj = "123";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

JDK21 新 switch:

复制代码
String res = switch (obj) {
    case null -> "空值";
    case Integer i when i > 100 -> "大数整数";
    case String s -> "字符串长度:" + s.length();
    default -> "其他类型";
};

3. JEP440 Record 记录模式(嵌套解构 Record)

配合 Record 类,直接解构内部字段,支持多层嵌套 Record 拆解。

复制代码
record Point(int x, int y){}
record Line(Point start, Point end){}

Line line = new Line(new Point(1,2), new Point(3,4));
// 嵌套解构,直接拿到x1/y1/x2/y2
if(line instanceof Line(Point(int x1, int y1), Point(int x2, int y2))){
    System.out.println(x1 + "," + y1);
}

4. JEP431 序列集合 Sequenced Collections(统一有序集合 API)

新增三套接口:SequencedCollection / SequencedSet / SequencedMap,List、LinkedHashSet、LinkedHashMap 自动实现,统一首尾操作、反转 API。

JDK17 及以前写法(繁琐易错):

复制代码
List<String> list = List.of("A","B","C");
String first = list.get(0);
String last = list.get(list.size()-1);
List<String> reverse = new ArrayList<>(list);
Collections.reverse(reverse);

JDK21 简化:

复制代码
SequencedCollection<String> seq = List.of("A","B","C");
seq.getFirst();
seq.getLast();
seq.reversed(); // 返回逆序视图
// LinkedHashMap 支持头部插入、弹出尾部(消息列表、最近访问缓存)
SequencedMap<String,Object> map = new LinkedHashMap<>();
map.putFirst("k1",v1);
map.pollLastEntry();

业务场景:最近操作记录、时序任务列表、有序缓存。

5. JEP439 分代 ZGC(Generational ZGC,正式稳定)

原 ZGC 是非分代,所有对象统一回收;JDK21 分代 ZGC 把堆分为年轻代、老年代

  1. 短生命周期对象(临时请求、临时 DTO)在年轻代快速回收,减少老年代扫描;
  2. 停顿时间依旧亚毫秒(<10ms),吞吐量提升 20%+,内存占用降低 25%;
  3. 高并发、大量临时对象的业务(业务运行过程中只使用一次、用完就丢弃、生命周期极短的对象,执行完方法就失去引用,很快要被 GC 回收,这类就是临时对象。)GC 压力大幅下降。

为什么这类业务 GC 压力大(JDK17 及之前不分代 ZGC/G1)?

  1. 短命对象全部堆在一起,老年代、年轻代不区分;
  2. 大量临时对象塞满堆,频繁触发 Minor GC;
  3. GC 需要扫描全部堆内存标记存活对象,频繁 STW 停顿,批量任务、定时任务容易卡顿;
  4. 部分临时对象晋升到老年代,老年代慢慢填满,触发开销更大的 Full GC。

JDK21 分代 ZGC 怎么降低 GC 压力?

分代 ZGC 把堆拆成年轻代(新生代)+ 老年代

  1. 所有临时小对象优先分配到年轻代;
  2. 年轻代回收速度极快,专门清理短命对象,扫描范围极小,停顿极低;
  3. 临时对象几乎不会晋升到老年代,老年代只存长期驻留数据(缓存、持久业务对象);
  4. 减少全堆扫描,GC 频率、单次 GC 耗时同步下降。

启动参数:

复制代码
java -XX:+UseZGC -XX:+ZGenerational -Xmx8g -jar app.jar
相关推荐
geovindu11 小时前
java:Abstract Factory Pattern
java·开发语言·后端·设计模式·抽象工厂模式·创建型模式
geovindu11 小时前
java: Factory Method Pattern
java·开发语言·后端·设计模式·工厂方法模式·创建型模式
程序员小八77711 小时前
如何自研一个全局异常处理器
java
风中的小甜瓜11 小时前
ASP.NET Page 那点事
java·前端·asp.net
爱敲代码的小鱼11 小时前
springsecurity:
java·开发语言·数据库
豆沙小饼干11 小时前
MySQL 小白循序渐进实操路线(纯动手,跟着敲就能练,全程生活化案例:外卖平台)
java
Wang's Blog11 小时前
Java框架快速入门: Spring Bean核心配置与实例化详解
java·开发语言·spring
爱敲代码的小鱼11 小时前
springboot原理篇:
java·spring boot·spring
zzzll111112 小时前
如何学习AI Agent?
java·人工智能·学习·程序员·大模型
阿pin12 小时前
Java随笔-GC收集器工作机制
java·开发语言·jvm·gc