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自动等待全部任务完成自动关闭对业务的价值不用手动配置corePoolSize/maxPoolSize/queue省去线程池调优成本IO 密集型任务吞吐量大幅提升天然适配 “一请求一线程”不用异步回调、CompletableFuture 嵌套代码同步写法更简单什么是 IO 密集型 / CPU 密集型IO 密集型程序大部分时间卡在等待外部资源CPU 空闲没事干网络 IORTSP 拉流、Feign 调工作流、HTTP 请求大模型、Redis、MySQL 查询文件 IOFFmpeg 读写视频、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 ThreadJVM 层面轻量化逻辑线程不直接绑定 OS 线程只是一段任务逻辑本身不占用操作系统资源可创建百万个。核心关系M:N 映射N 个虚拟线程复用 M 个载体线程M 远小于 N。同一时刻一个载体线程只能执行一个虚拟线程。大白话举例载体线程 食堂厨师只有 4 个虚拟线程 待处理的任务几千上万个任务 A 正在厨师 1 手上处理中途要等外部 AI 接口返回IO 阻塞厨师 1 不用干等放下任务 A挂起虚拟线程去处理任务 B等 AI 接口数据回来任务 A 再排队有空厨师再接着处理。传统线程对比 传统平台线程执行 IO 阻塞厨师会卡死在这个任务上啥也干不了只能新增更多厨师线程。IO 完成后如何恢复当操作系统返回 IO 数据网络 / 数据库响应JVM 收到 IO 就绪通知该虚拟线程重新进入就绪队列载体线程空闲时再次绑定它从阻塞处继续往下执行剩余业务代码。补充两个关键点1什么时候载体线程不会释放如果使用synchronized同步块锁住代码虚拟线程阻塞在锁竞争时不会解绑载体线程载体线程被占用无法复用会丧失虚拟线程优势。2CPU 密集场景无复用效果如果虚拟线程全是纯计算无任何 IO 阻塞虚拟线程会一直占用载体线程跑计算载体无法释放此时虚拟线程没有性能提升。2. JEP441 Switch 模式匹配转正简化类型判断告别instanceof 强转样板代码支持类型匹配、null 匹配、when 条件判断。旧写法Object obj 123; if (obj instanceof String) { String s (String) obj; System.out.println(s.length()); }JDK21 新 switchString 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 / SequencedMapList、LinkedHashSet、LinkedHashMap 自动实现统一首尾操作、反转 API。JDK17 及以前写法繁琐易错ListString list List.of(A,B,C); String first list.get(0); String last list.get(list.size()-1); ListString reverse new ArrayList(list); Collections.reverse(reverse);JDK21 简化SequencedCollectionString seq List.of(A,B,C); seq.getFirst(); seq.getLast(); seq.reversed(); // 返回逆序视图 // LinkedHashMap 支持头部插入、弹出尾部消息列表、最近访问缓存 SequencedMapString,Object map new LinkedHashMap(); map.putFirst(k1,v1); map.pollLastEntry();业务场景最近操作记录、时序任务列表、有序缓存。5. JEP439 分代 ZGCGenerational ZGC正式稳定原 ZGC 是非分代所有对象统一回收JDK21 分代 ZGC 把堆分为年轻代、老年代短生命周期对象临时请求、临时 DTO在年轻代快速回收减少老年代扫描停顿时间依旧亚毫秒10ms吞吐量提升 20%内存占用降低 25%高并发、大量临时对象的业务业务运行过程中只使用一次、用完就丢弃、生命周期极短的对象执行完方法就失去引用很快要被 GC 回收这类就是临时对象。GC 压力大幅下降。为什么这类业务 GC 压力大JDK17 及之前不分代 ZGC/G1短命对象全部堆在一起老年代、年轻代不区分大量临时对象塞满堆频繁触发 Minor GCGC 需要扫描全部堆内存标记存活对象频繁 STW 停顿批量任务、定时任务容易卡顿部分临时对象晋升到老年代老年代慢慢填满触发开销更大的 Full GC。JDK21 分代 ZGC 怎么降低 GC 压力分代 ZGC 把堆拆成年轻代新生代 老年代所有临时小对象优先分配到年轻代年轻代回收速度极快专门清理短命对象扫描范围极小停顿极低临时对象几乎不会晋升到老年代老年代只存长期驻留数据缓存、持久业务对象减少全堆扫描GC 频率、单次 GC 耗时同步下降。启动参数java -XX:UseZGC -XX:ZGenerational -Xmx8g -jar app.jar