Java新特性(六):Java 22 ~ 27
Java新特性(六):Java 22 ~ 27
导语:Java 22 ~ 27 的核心脉络只有三条:语言侧把模式匹配做到底(原始类型模式、未命名变量)、并发侧补齐虚拟线程生态(ScopedValue、结构化并发、无 pinning)、运行时侧押注启动速度与内存占用(AOT 缓存、紧凑对象头)。其中 Java 24 的 JEP 491 修正了「虚拟线程不能用 synchronized」的过时结论,是本篇最值得记住的一条。由于 JDK 26/27 刚发布不久,面试中一般只要求「了解方向」。
一、Java 22:FFM 转正与「简化语法」三连
1. FFM API(外部函数与内存 API)为什么重要?(高频)
答: JEP 454 在 Java 22 正式转正,历经 Java 17/18 孵化、19/20/21 三轮预览。它一次性替代了两个「历史遗留方案」:
- 替代 JNI:JNI 需要写 C 代码、编译动态库、跨平台维护成本极高、出错直接崩溃 JVM;
- 替代
sun.misc.Unsafe:Unsafe 的堆外内存操作无边界检查、无生命周期管理,极易内存泄漏或越界。
FFM API 的核心组件:
| 组件 | 作用 |
|---|---|
Linker | 链接本地函数,把 Java 方法描述符映射为本地函数调用 |
SymbolLookup | 在本地库中查找符号(函数地址) |
MemorySegment | 安全的堆外内存段(带边界与生命周期) |
Arena | 内存生命周期管理(ofConfined/ofShared/ofAuto),关闭即释放 |
MemoryLayout / ValueLayout | 描述内存布局(C 结构体映射) |
MethodHandle | 调用本地函数(downcallHandle)/ 供本地调用 Java(upcallStub) |
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class FfmDemo {
public static void main(String[] args) throws Throwable {
// 1. 拿到 C 标准库的 strlen 函数句柄
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
// 2. 在堆外分配内存(Arena 管理生命周期,try-with-resources 自动释放)
try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateFrom("Hello FFM");
long len = (long) strlen.invoke(cString);
System.out.println("strlen = " + len); // 9
} // 离开作用域,堆外内存被释放
// 3. 结构化内存布局:模拟 C 的 struct { int x; int y; }
StructLayout pointLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y"));
try (Arena arena = Arena.ofConfined()) {
MemorySegment point = arena.allocate(pointLayout);
point.set(ValueLayout.JAVA_INT, 0, 3); // x = 3
point.set(ValueLayout.JAVA_INT, 4, 4); // y = 4
VarHandle xHandle = pointLayout.varHandle(
MemoryLayout.PathElement.groupElement("x"));
System.out.println("x = " + xHandle.get(point));
}
}
}面试价值点:FFM 是「Java 与原生世界互操作」的官方答案,也是很多高性能库(Netty 的堆外内存、Lucene、Kafka、JDK 自身的加密/压缩)迁移的方向;配合 Java 23 弃用 Unsafe 内存访问(JEP 471)一起考。
2. Java 22 还有哪些「简化语法」?
答:
- 未命名模式与变量转正(JEP 456):
_表示「我不关心这个值」,可用于模式、catch、for、lambda:
try {
risky();
} catch (Exception _) { // 不关心异常对象,避免 unused 警告
log("failed");
}
for (var _ : list) { ... } // 不关心元素
if (obj instanceof Point(int x, _)) { } // 只解构 x- 多文件源码启动(JEP 458):
java Main.java会自动编译同目录被引用的其它源文件,写小工具不必再配javac:
java Main.java # 自动编译并链接同目录下的 Helper.java- G1 区域固定(JEP 423):过去 JNI 的
GetPrimitiveArrayCritical会固定住整个 Region(甚至整堆)导致 GC 停顿飙升,现在只固定必要的区域,其余 Region 照常回收。
二、Java 23:文档、GC 与「弃用 Unsafe」
3. Markdown 文档注释(JEP 467)
答: 文档注释终于可以直接写 Markdown,不再被迫写 <p>、{@code} 这类 HTML/JavaDoc 标签(两种风格可混用,向后兼容):
/**
* 订单服务。
*
* ## 用法
*
* ```java
* var service = new OrderService();
* service.create(new Order("o1"));
* ```
*
* | 方法 | 说明 |
* | --- | --- |
* | {@code create} | 创建订单 |
* | {@code cancel} | 取消订单 |
*
* @param id 订单 ID
*/
public void create(Order order) { }意义:让文档与代码同源、可被 IDE 与静态站点直接渲染,降低维护成本。它是「正式」特性(不需要预览开关)。
4. 为什么说「ZGC 默认分代」是个信号?
答: JEP 474 把 ZGC 的默认模式改为分代,非分代模式被弃用并计划移除。把这条与「G1 自 Java 9 起成为默认 GC」「分代 Shenandoah 在 Java 25 转正」「Java 27 起所有环境统一默认 G1」放在一起看,JVM 的方向非常明确:在保持低延迟的前提下追求分代带来的吞吐提升。
5. 弃用 sun.misc.Unsafe 内存访问(JEP 471)
答: Unsafe 的堆外内存访问方法(allocateMemory、freeMemory、putLong、getObject 等)在 Java 23 被标记弃用,Java 24 起调用会打印运行时警告,未来版本将移除。
官方指定的迁移路径(这是高频追问点):
| 旧用法 | 新方案 | 引入版本 |
|---|---|---|
Unsafe 操作堆内字段/数组(含 volatile、CAS) | VarHandle | Java 9(JEP 193) |
Unsafe 操作堆外内存 | MemorySegment / Arena(FFM) | Java 22(JEP 454) |
Unsafe 调用本地代码 | Linker(FFM) | Java 22(JEP 454) |
// 旧:Unsafe
// long addr = unsafe.allocateMemory(16);
// unsafe.putLong(addr, 42L);
// 新:FFM + VarHandle
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg = arena.allocate(ValueLayout.JAVA_LONG, 2);
VarHandle handle = ValueLayout.JAVA_LONG.arrayElementVarHandle();
handle.setVolatile(seg, 0L, 42L); // 带内存语义的写入
System.out.println((long) handle.getVolatile(seg, 0L));
}6. Java 23 的其他要点
答:
- 原始类型模式首次预览(JEP 455):
instanceof与switch开始支持int、long、boolean等基本类型模式,后续在 JDK 25 / 26 / 27 持续预览:
static String classify(Object obj) {
return switch (obj) {
case int i when i > 0 -> "正整数";
case int i -> "非正整数";
case boolean b -> "布尔值 " + b;
default -> "其它";
};
}- Stream Gatherers 第二次预览(JEP 473)、Class-File API 第二次预览(JEP 466)、模块导入声明预览(JEP 476)、未命名类与实例 main 第三次预览(JEP 477)、结构化并发第三次预览(JEP 480)、Scoped Values 第三次预览(JEP 481)、灵活构造函数体第二次预览(JEP 482);Vector API 第八次孵化(JEP 469)。
三、Java 24:虚拟线程补齐短板
7. JEP 491:虚拟线程在 synchronized 中不再固定载体线程(重点)
答: 这是 Java 24 最有价值的变更,因为它推翻了流传很广的旧结论。
- 问题:Java 21 ~ 23 中,虚拟线程若在
synchronized代码块内阻塞(I/O、Thread.sleep等),会把它的载体线程「钉住(pin)」,此时 JVM 无法把该虚拟线程卸载,载体线程被白白占用。如果应用里大量使用synchronized(老代码、部分 JDBC 驱动、某些库),并发度会急剧下降,甚至死锁式停滞。 - 旧应对:把
synchronized全部换成ReentrantLock(一种普遍但不优雅的迁移)。 - Java 24 的修复:JVM 重新实现了对象监视器(Object Monitor),让虚拟线程在
synchronized中阻塞时也能正常挂起并释放载体线程——现有代码零改动即受益。
// JDK 21 ~ 23:这里阻塞会 pin 住载体线程,并发能力受限
// JDK 24+:阻塞时会正常卸载,无需改写代码
synchronized (lock) {
remoteService.call(); // 阻塞 I/O
}面试标准回答:
「虚拟线程早期(21 ~ 23)确实存在
synchronized阻塞导致 pinning 的问题,当时的建议是改用ReentrantLock;JDK 24 的 JEP 491 已经修复,虚拟线程在synchronized中阻塞也能卸载载体线程,所以『虚拟线程不能用synchronized』是过时结论。不过仍有少量场景会 pin(如本地方法调用、Object.wait的部分路径),排查时可以用-Djdk.tracePinnedThreads=full(21~23)或 JFR 事件确认。」
8. Stream Gatherers 转正(JEP 485)
答: Stream 的中间操作终于可以自定义了。以前 Stream 只有固定的 filter/map/flatMap 等,想做「滑动窗口」「按规则批量聚合」只能退化成 collect + 循环。Gatherers 引入 Stream.gather(Gatherer),让自定义中间操作像 filter 一样可组合、可并行(Gatherer.of 可声明并发能力)。
import java.util.*;
import java.util.stream.*;
public class GathererDemo {
public static void main(String[] args) {
// 1. 固定窗口:每 3 个元素一组(内置 Gatherer)
List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5, 6, 7)
.gather(Gatherer.windowFixed(3))
.toList();
System.out.println(windows); // [[1, 2, 3], [4, 5, 6], [7]]
// 2. 滑动窗口:窗口大小 3、步长 1
List<List<Integer>> sliding = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherer.windowSliding(3))
.toList();
System.out.println(sliding); // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
// 3. 自定义 Gatherer:按「字符串长度」去重(只保留每个长度第一次出现的值)
List<String> distinctByLength = Stream.of("foo", "bar", "baz", "quux")
.gather(Gatherer.ofSequential(
() -> new HashSet<Integer>(), // 初始状态:已见过的长度
(Set<Integer> seen, String s, Gatherer.Downstream<? super String> ds) ->
seen.add(s.length()) ? ds.push(s) : true // true 表示继续处理
))
.toList();
System.out.println(distinctByLength); // [foo, quux]
}
}Gatherer 的四个组成部分(面试加分项):initializer(初始状态)、integrator(逐个处理元素)、combiner(并行合并)、finisher(收尾输出)。
9. Java 24 的其他要点
答:
- Class-File API 转正(JEP 484):标准化的类文件解析/生成/转换 API,目标是取代 ASM 等第三方库(框架、字节码增强工具的方向)。
- 永久禁用 Security Manager(JEP 486):不再允许启用(连
-Djava.security.manager也不行),历史包袱彻底卸下。 - AOT 类加载与链接(JEP 483):通过「训练运行(record)+ 启动加载缓存」提前完成类加载与链接,Spring PetClinic 基准中启动时间最多缩短约 42%。
# 三步走:训练运行 → 生成缓存 → 使用缓存启动
java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -jar app.jar
java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar app.jar
java -XX:AOTCache=app.aot -jar app.jar- 紧凑对象头(JEP 450,实验):64 位平台上对象头从 12~16 字节压缩到 8 字节,减少堆占用。JDK 25 转正,JDK 27 起默认开启。
# JDK 24/25 显式开启
java -XX:+UseCompactObjectHeaders -jar app.jar- ZGC 移除非分代模式(JEP 490)、KDF API 预览(JEP 478)、ML-DSA 后量子签名(JEP 497)、Unsafe 内存访问警告(JEP 498),以及结构化并发/Scoped Values/基本类型模式/模块导入/灵活构造器等一系列预览的第四次迭代。
四、Java 25(LTS):新特性正式化
10. ScopedValue vs ThreadLocal(必问)
答: ScopedValue(JEP 506,Java 25 转正)是「线程内/线程间共享不可变上下文」的现代方案,专为大量虚拟线程设计。
| 维度 | ThreadLocal | ScopedValue |
|---|---|---|
| 可变性 | 可读可写(set/remove) | 只读(绑定后不可改) |
| 生命周期 | 绑定到线程,需手动 remove,否则泄漏 | 结构化作用域:run/call 结束即自动失效 |
| 传递方向 | 双向(任何地方都能读、都能改) | 单向(调用者 → 被调用者,隐式传参) |
| 内存开销 | 每个线程一个 ThreadLocalMap 条目,百万虚拟线程时开销可观 | 共享同一个不可变绑定快照,几乎零拷贝 |
| 继承 | InheritableThreadLocal 复制到子线程(易失控) | 结构化并发创建的子线程可直接继承,无需拷贝 |
| 适用场景 | 传统平台线程、需可变缓存 | 请求上下文(用户、租户、追踪 ID)、鉴权信息 |
public class ScopedValueDemo {
static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();
static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();
public static void main(String[] args) {
// 绑定一个值并执行(run 无返回值,call 有返回值)
ScopedValue.where(CURRENT_USER, "张三").run(() -> {
System.out.println("外层:" + CURRENT_USER.get());
// 嵌套绑定:内层作用域可见两个值,退出后自动恢复外层
ScopedValue.where(TRACE_ID, "trace-001").run(() -> {
System.out.println("内层:" + CURRENT_USER.get() + ", " + TRACE_ID.get());
});
System.out.println("内层已结束,TRACE_ID 不可访问:" + TRACE_ID.isBound()); // false
});
System.out.println("作用域外:" + CURRENT_USER.isBound()); // false
// 带返回值
String result = ScopedValue.where(CURRENT_USER, "李四")
.call(() -> "hello " + CURRENT_USER.get());
System.out.println(result);
}
}与虚拟线程的配合:结构化并发(StructuredTaskScope)fork 出的子任务会自动继承父线程的 ScopedValue 绑定,无需像 InheritableThreadLocal 那样复制整份 map——这就是它在「百万虚拟线程 + 请求上下文」场景下明显优于 ThreadLocal 的原因。
11. Java 25 其他转正的特性
答:
- 紧凑源文件与实例 main 方法(JEP 512,转正):初学者与小脚本可以这样写:
// Hello.java —— 无需 class 声明、无需 public static
void main() {
System.out.println("Hello, Java 25!");
}java Hello.java- 模块导入声明(JEP 511,转正):一行导入整个模块导出的所有包,减少一堆 import:
import module java.base; // 自动可用 List、Map、Stream、Function 等
import module java.sql;
public class Example {
public static void main(String[] args) {
Map<String, String> map = Stream.of("a", "b")
.collect(Collectors.toMap(s -> s.toUpperCase(), Function.identity()));
System.out.println(map);
}
}- 灵活构造函数体(JEP 513,转正):允许在
super(...)/this(...)之前写语句(可校验参数、初始化字段,但不能引用正在构造的实例),解决过去「子类校验参数要先塞进静态方法」的别扭写法:
class Person {
private final String name;
Person(String name, int age) {
if (age < 0) { // ✅ 以前这里不能写语句
throw new IllegalArgumentException("age < 0");
}
this.name = name;
}
}
class Employee extends Person {
private final int id;
Employee(String name, int age, int id) {
this.id = id; // ✅ super 之前可以先初始化自己的字段
super(name, age);
}
}- 紧凑对象头转正(JEP 519):
-XX:+UseCompactObjectHeaders(仍非默认,JDK 27 默认开启)。 - 分代 Shenandoah 转正(JEP 521):
-XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational。 - KDF API 转正(JEP 478):标准化的密钥派生接口(如 HKDF),避免同一密钥被复用于不同用途。
- 仍在预览/孵化:结构化并发第五次预览(JEP 505)、基本类型模式第三次预览(JEP 507)、Vector API 第十次孵化(JEP 508)、PEM 编码 API 预览(JEP 470)。
五、Java 26 / 27:最新两个版本(了解即可)
12. Java 26(2026-03)的重点
答: 共 10 个 JEP(5 正式 + 4 预览 + 1 孵化):
| JEP | 名称 | 状态 | 要点 |
|---|---|---|---|
| 517 | HTTP/3 for the HTTP Client | 正式 | 基于 QUIC/UDP,新增 HttpClient.Version.HTTP_3,0-RTT、消除队头阻塞、连接迁移,且能自动降级到 HTTP/2、HTTP/1.1 |
| 516 | AOT 对象缓存 | 正式 | 在 24 的「类加载缓存」基础上进一步缓存对象实例,且支持任意 GC;典型 Spring Boot 应用启动时间可降到 1 秒内 |
| 522 | G1 GC 吞吐提升 | 正式 | 双卡表(Dual Card Table)减少写屏障同步开销,高写入场景吞吐提升(官方口径 5% ~ 15%),零配置生效 |
| 500 | final 字段完整性 | 正式 | 反射修改 final 字段开始运行时警告,为将来禁止铺垫;迁移方案:record、构造器注入、VarHandle |
| 504 | 移除 Applet API | 正式 | java.applet 包彻底删除(Java 17 起标记为待移除) |
| 525 | 结构化并发 | 第六次预览 | 新增超时支持(Joiner.onTimeout),可「竞速取最快」 |
| 530 | 基本类型模式 | 第四次预览 | instanceof/switch 支持全部原始类型 |
| 526 | 延迟常量 Lazy Constants | 第二次预览 | 线程安全的惰性初始化,替代双重检查锁 / Holder 模式 |
| 524 | PEM 编码 API | 第二次预览 | 密码学对象与 PEM 文本互转 |
| 529 | Vector API | 第十一次孵化 | 等 Valhalla 值类型落地后才可能标准化 |
13. Java 27(2026-09-15 GA)的重点
答: 共 9 个 JEP(4 预览 + 1 孵化 + 4 正式):
| JEP | 名称 | 状态 | 要点 |
|---|---|---|---|
| 523 | G1 成为所有环境默认 GC | 正式 | 统一各部署环境的默认行为,减少迁移与调优差异 |
| 534 | 紧凑对象头默认开启 | 正式 | 对象头 12~16 字节 → 8 字节,升级即省内存,大规模部署密度提升 |
| 527 | TLS 1.3 后量子混合密钥交换 | 正式 | 抵御「先收集、后解密」攻击,为后量子密码迁移做准备 |
| 536 | JFR 进程内数据脱敏 | 正式 | 在保留诊断能力的前提下降低敏感信息泄露风险 |
| 533 | 结构化并发 | 第七次预览 | 优化取消、异常处理与可观测性 |
| 532 | 基本类型模式 | 第五次预览 | —— |
| 531 | 延迟常量 | 第三次预览 | —— |
| 538 | PEM 编码 API | 第三次预览 | —— |
| 537 | Vector API | 第十二次孵化 | —— |
版本提醒:JDK 26 / 27 均为非 LTS,面试中出现概率很低。回答「Java 26/27 有哪些新特性」时,能说出HTTP/3、AOT 对象缓存、G1 双卡表、紧凑对象头默认开启就已经很有竞争力,不必逐条背诵。
六、升级选型与面试收尾
14. 项目该选哪个版本?8 → 21 还是 8 → 25?
答:
- 生产选型看 LTS + 生态成熟度:目前(2026 年)Java 21 是兼顾「新特性 + 生态稳定」的首选;Java 25 作为新 LTS 正在被 Spring Boot 3.5+/4.x、主流中间件跟进,是新项目的前瞻选择;
- Java 8 → 21 的迁移收益最大:虚拟线程(I/O 密集型吞吐提升显著)、模式匹配、record/密封类、GC 与启动优化;
- Java 8 仍然不能随便丢:大量存量系统与老中间件客户端依赖它,通常是「新服务用 21/25,老服务按需升级」。
15. 「你项目里用过哪些 Java 新特性?」怎么答
答: 不要罗列版本号,要按「解决了什么问题」来答,示例模板:
「我们的服务跑在 JDK 21 上。并发侧:用虚拟线程 + Spring Boot 的
spring.threads.virtual.enabled处理高并发 I/O 请求,配合Semaphore对数据库连接做限流,QPS 提升明显但连接池没被打爆;代码侧:DTO 和查询结果统一用record,配合密封接口 + switch 模式匹配替代大量if-else instanceof;集合操作用Stream.toList()和 Sequenced Collections 简化首尾操作;工具链用--release 21统一编译目标,并用jdeps --jdk-internals扫过内部 API 依赖,升级时用--add-opens兜底了少数反射场景。」
这样的回答同时展现了语言特性、并发模型、性能与迁移风险四层认知,比背特性清单加分得多。
16. 全系列速查表(Java 8 → 27)
| 版本 | LTS | 必记特性 |
|---|---|---|
| 8 | ✅ | Lambda、函数式接口、Stream、Optional、默认方法、java.time、CompletableFuture、Metaspace |
| 9 | ❌ | 模块系统 JPMS、List.of、接口私有方法、takeWhile/dropWhile、G1 默认 |
| 10 | ❌ | var(局部变量类型推断,非动态类型) |
| 11 | ✅ | HTTP Client、String 增强、单文件运行、ZGC/Epsilon/JFR 开源 |
| 12 ~ 13 | ❌ | switch 表达式预览、文本块预览、yield、Shenandoah |
| 14 | ❌ | switch 表达式转正、Helpful NPE、CMS 移除、jpackage |
| 15 | ❌ | 文本块转正、密封类预览、隐藏类、EdDSA、移除 Nashorn |
| 16 | ❌ | record 转正、instanceof 模式匹配转正、强封装内部 API、Stream.toList() |
| 17 | ✅ | 密封类转正、switch 模式匹配预览、增强伪随机数、恢复严格浮点 |
| 18 ~ 20 | ❌ | 默认 UTF-8、jwebserver、虚拟线程/结构化并发/FFM 预览孵化 |
| 21 | ✅ | 虚拟线程转正、模式匹配 switch + record 模式转正、Sequenced Collections、分代 ZGC |
| 22 | ❌ | FFM API 转正、未命名变量转正、多文件源码启动、G1 区域固定 |
| 23 | ❌ | Markdown 文档注释、ZGC 默认分代、弃用 Unsafe 内存访问、基本类型模式预览 |
| 24 | ❌ | 虚拟线程无 pinning(JEP 491)、Stream Gatherers 转正、Class-File API 转正、AOT 类加载 |
| 25 | ✅ | ScopedValue 转正、紧凑源文件、模块导入声明、灵活构造器、紧凑对象头、分代 Shenandoah |
| 26 | ❌ | HTTP/3、AOT 对象缓存、G1 双卡表、final 字段反射警告、移除 Applet |
| 27 | ❌ | G1 全环境默认、紧凑对象头默认开启、后量子 TLS、JFR 脱敏 |
最后提醒:面试里不要背版本号。优先掌握 Java 8(必会)→ Java 17 / 21(主流生产)→ Java 25(新 LTS) 这条主线上的特性,能讲清「解决什么问题 + 怎么用 + 有什么坑」,远胜于把 20 个版本的 JEP 编号逐个报出来。
