Java新特性与GraalVM¶
🎯 学习目标¶
学习时间:2-3周 | 难度:⭐⭐⭐⭐⭐ 高级
完成本章学习后,你将能够: - 掌握Java 22-25的核心新特性(Unnamed Patterns、String Templates、Scoped Values等) - 深入理解Structured Concurrency与Stream Gatherers - 熟练使用GraalVM将Spring Boot应用编译为Native Image - 掌握GraalVM反射配置、Resource配置与构建优化 - 理解虚拟线程(Loom项目)的底层原理与实战用法 - 掌握Project Panama(FFM API)实现Java与C互操作
📖 目录¶
- Java 22新特性
- Java 23新特性
- Java 24-25新特性
- GraalVM与Native Image
- 虚拟线程深入(Loom项目实战)
- Project Panama(FFM API与C互操作)
- 面试题
1. Java 22新特性¶
Java 22(2024年3月发布)带来了多项重要特性从预览走向正式。
1.1 Unnamed Patterns and Variables(JEP 456,正式)¶
未命名模式和变量允许使用 _ 表示"不关心"的变量,减少无用变量声明。
// ============ 旧写法:被迫声明但不使用的变量 ============
try {
int value = Integer.parseInt(input);
} catch (NumberFormatException ex) { // ex从未使用
System.out.println("Invalid number");
}
for (var entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue(); // value未使用
process(key);
}
// ============ 新写法:使用 _ 表示未命名变量 ============
try {
int value = Integer.parseInt(input);
} catch (NumberFormatException _) { // 明确表示不关心异常对象
System.out.println("Invalid number");
}
for (var entry : map.entrySet()) {
String key = entry.getKey();
Object _ = entry.getValue(); // 明确表示不关心value
process(key);
}
// ============ 在Pattern Matching中的应用 ============
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double a, double b, double c) implements Shape {}
// 只关心部分组件
static String describe(Shape shape) {
return switch (shape) {
case Circle(var radius) -> "圆形,半径: " + radius;
case Rectangle(var w, var _) -> "矩形,宽: " + w; // 不关心高度
case Triangle(_, _, _) -> "三角形"; // 不关心任何参数
};
}
// ============ Lambda中的应用 ============
// 旧写法
map.forEach((key, value) -> System.out.println(key)); // value未使用
// 新写法
map.forEach((key, _) -> System.out.println(key));
// 多个未命名变量可以共存
map.computeIfAbsent(key, _ -> new ArrayList<>()); // 参数不使用
queue.forEach(_ -> counter.incrementAndGet()); // 不关心元素
1.2 Stream Gatherers(JEP 461,预览)¶
Stream Gatherers为Stream API引入自定义中间操作的能力,弥补了Stream API中间操作不可扩展的短板。
// ============ 内置Gatherer示例 ============
import java.util.stream.Gatherers;
// 1. 固定窗口分组(windowFixed)
List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
.gather(Gatherers.windowFixed(3)) // 每3个元素一组
.toList();
// 结果: [[1,2,3], [4,5,6], [7,8]]
// 2. 滑动窗口(windowSliding)
List<List<Integer>> sliding = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowSliding(3)) // 滑动窗口大小3
.toList();
// 结果: [[1,2,3], [2,3,4], [3,4,5]]
// 3. 折叠(fold)— 有状态的归约
Optional<String> joined = Stream.of("a", "b", "c")
.gather(Gatherers.fold(
() -> "", // 初始值
(state, element) -> state.isEmpty() ? element : state + "," + element
))
.findFirst();
// 结果: "a,b,c"
// 4. 扫描(scan)— 类似fold但输出每个中间状态
List<Integer> runningSum = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.scan(() -> 0, Integer::sum))
.toList();
// 结果: [1, 3, 6, 10, 15]
// 5. mapConcurrent — 并发映射(结合虚拟线程)
List<String> results = urls.stream()
.gather(Gatherers.mapConcurrent(10, url -> fetchContent(url))) // 最大并发10
.toList();
// ============ 自定义Gatherer ============
/**
* 自定义Gatherer:去重连续重复元素(类似Unix uniq命令)
*/
static <T> Gatherer<T, ?, T> distinctConsecutive() {
// Gatherer.ofSequential(initializer, integrator, finisher)
return Gatherer.ofSequential(
// 初始化状态:保存上一个元素
() -> new Object() { T last = null; boolean hasLast = false; },
// 积分器:决定是否向下游推送
(state, element, downstream) -> {
if (!state.hasLast || !Objects.equals(state.last, element)) {
state.last = element;
state.hasLast = true;
return downstream.push(element);
}
return true; // 跳过重复元素,继续处理
}
);
}
// 使用
List<Integer> result = Stream.of(1, 1, 2, 2, 2, 3, 1, 1)
.gather(distinctConsecutive())
.toList();
// 结果: [1, 2, 3, 1]
/**
* 自定义Gatherer:限流(每N个元素取1个)
*/
static <T> Gatherer<T, ?, T> everyNth(int n) {
return Gatherer.ofSequential(
() -> new Object() { int count = 0; },
(state, element, downstream) -> {
if (state.count++ % n == 0) {
return downstream.push(element);
}
return true;
}
);
}
// 使用
List<Integer> sampled = IntStream.rangeClosed(1, 20).boxed()
.gather(everyNth(5))
.toList();
// 结果: [1, 6, 11, 16]
1.3 Class-File API(JEP 457,预览)¶
Class-File API提供标准的字节码读写API,替代第三方库(ASM/Javassist)。
import java.lang.classfile.*;
import java.lang.classfile.attribute.*;
// ============ 读取Class文件 ============
ClassModel classModel = ClassFile.of().parse(
Path.of("target/classes/com/example/MyClass.class")
);
// 遍历类信息
System.out.println("类名: " + classModel.thisClass().asInternalName());
System.out.println("父类: " + classModel.superclass()
.map(ClassEntry::asInternalName).orElse("java/lang/Object"));
// 遍历方法
for (MethodModel method : classModel.methods()) {
System.out.println("方法: " + method.methodName()
+ " " + method.methodType());
}
// ============ 动态生成Class文件 ============
byte[] bytes = ClassFile.of().build(
ClassDesc.of("com.example.Generated"),
classBuilder -> {
// 添加字段
classBuilder.withField("name", ClassDesc.ofDescriptor("Ljava/lang/String;"),
ClassFile.ACC_PRIVATE);
// 添加方法
classBuilder.withMethod("getName",
MethodTypeDesc.of(ClassDesc.ofDescriptor("Ljava/lang/String;")),
ClassFile.ACC_PUBLIC,
methodBuilder -> methodBuilder.withCode(codeBuilder -> {
codeBuilder.aload(0)
.getfield(ClassDesc.of("com.example.Generated"),
"name", ClassDesc.ofDescriptor("Ljava/lang/String;"))
.areturn();
})
);
}
);
2. Java 23新特性¶
Java 23(2024年9月发布)继续打磨多项特性。
2.1 String Templates(已撤回)¶
⚠️ 重要提醒:String Templates(JEP 459/465)在 Java 21-22 中作为预览特性引入,但在 Java 23 中已被完全撤回(Withdrawn)。
STR、FMT模板处理器和StringTemplateAPI 已从 Java 23 及后续版本中移除,以下代码无法在 Java 23+ 上编译。仅作历史参考。
// ⚠️ 以下代码仅适用于 Java 21-22 预览模式,Java 23+ 已移除此特性
// 目前推荐使用 String.format() 或 MessageFormat 进行字符串格式化
// 曾经的 STR 模板处理器语法(已撤回):
// String greeting = STR."Hello, \{name}! You are \{age} years old.";
// 当前推荐的替代方案:
String name = "World";
int age = 25;
String greeting = "Hello, %s! You are %d years old.".formatted(name, age);
// 或
String greeting2 = String.format("Hello, %s! You are %d years old.", name, age);
2.2 Scoped Values(JEP 464 预览 → JEP 506 Java 25正式化)¶
Scoped Values是ThreadLocal的现代替代方案,专为虚拟线程设计,具有不可变性和自动清理特性。
import java.lang.ScopedValue;
// ============ 定义ScopedValue ============
public class RequestContext {
// 类似ThreadLocal,但不可变且作用域明确
public static final ScopedValue<UserInfo> CURRENT_USER = ScopedValue.newInstance();
public static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
}
// ============ 绑定值(在Web框架中间件中) ============
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
UserInfo user = authenticate(request);
String requestId = UUID.randomUUID().toString();
// ScopedValue.where(...).run(...) 设置作用域
ScopedValue.where(RequestContext.CURRENT_USER, user)
.where(RequestContext.REQUEST_ID, requestId)
.run(() -> {
try {
chain.doFilter(request, response);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// 离开run()块后,值自动清除 — 不会内存泄漏
}
}
// ============ 在任意深度的调用链中读取 ============
@Service
public class OrderService {
public OrderDTO createOrder(CreateOrderRequest request) {
// 直接读取 — 比ThreadLocal更高效(缓存友好)
UserInfo user = RequestContext.CURRENT_USER.get();
String reqId = RequestContext.REQUEST_ID.get();
log.info("[{}] User {} creating order", reqId, user.getName());
// ...
}
}
// ============ ScopedValue vs ThreadLocal对比 ============
/*
┌──────────────┬───────────────────────────┬──────────────────────────┐
│ 特性 │ ThreadLocal │ ScopedValue │
├──────────────┼───────────────────────────┼──────────────────────────┤
│ 可变性 │ 可读写(set/get) │ 只读(绑定后不可变) │
│ 生命周期 │ 手动remove() │ 自动(作用域结束即清理) │
│ 内存泄漏 │ 容易忘记remove导致泄漏 │ 不会泄漏 │
│ 继承 │ InheritableThreadLocal │ StructuredTaskScope继承 │
│ 虚拟线程 │ 性能差(每线程一个槽) │ 优化设计,性能好 │
│ 不可变保证 │ ❌ │ ✅ 可以安全缓存 │
└──────────────┴───────────────────────────┴──────────────────────────┘
*/
2.3 Structured Concurrency(向正式化推进)¶
结构化并发确保并发任务的生命周期与代码结构一致,避免线程泄漏。
import java.util.concurrent.StructuredTaskScope;
// ============ 基础用法:并行获取多个数据源 ============
public record UserProfile(UserInfo user, List<Order> orders, CreditScore credit) {}
public UserProfile fetchUserProfile(long userId) throws Exception {
// ShutdownOnFailure:任何子任务失败 → 取消所有其他子任务
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 并行提交三个子任务
Subtask<UserInfo> userTask = scope.fork(() -> userService.getUser(userId));
Subtask<List<Order>> ordersTask = scope.fork(() -> orderService.getOrders(userId));
Subtask<CreditScore> creditTask = scope.fork(() -> creditService.getScore(userId));
// 等待所有任务完成(或首个失败)
scope.join();
scope.throwIfFailed(); // 如果有任务失败,抛出异常
// 所有任务成功,获取结果
return new UserProfile(
userTask.get(),
ordersTask.get(),
creditTask.get()
);
}
// try-with-resources结束 → 确保所有子任务已终止
}
// ============ ShutdownOnSuccess:取最快响应 ============
public String fetchFromFastestMirror(List<String> mirrorUrls) throws Exception {
// 第一个成功的结果返回后,取消其余任务
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
for (String url : mirrorUrls) {
scope.fork(() -> httpClient.get(url));
}
scope.join();
return scope.result(); // 返回最快完成的结果
}
}
// ============ 自定义策略:收集部分结果 ============
public class CollectingScope<T> extends StructuredTaskScope<T> {
private final List<T> results = new CopyOnWriteArrayList<>();
private final int requiredCount;
public CollectingScope(int requiredCount) {
this.requiredCount = requiredCount;
}
@Override
protected void handleComplete(Subtask<? extends T> subtask) {
if (subtask.state() == Subtask.State.SUCCESS) {
results.add(subtask.get());
if (results.size() >= requiredCount) {
shutdown(); // 收集到足够数量后关闭
}
}
}
public List<T> results() {
return Collections.unmodifiableList(results);
}
}
// 使用:从5个节点并行查询,只需3个响应即可
try (var scope = new CollectingScope<QueryResult>(3)) {
for (String node : clusterNodes) {
scope.fork(() -> queryNode(node));
}
scope.join();
return scope.results(); // 返回最先完成的3个结果
}
3. Java 24-25新特性¶
Java 24(2025年3月)和Java 25(2025年9月,LTS)继续推进多项特性。
3.1 Primitive Types in Patterns(原始类型模式匹配)¶
// ============ switch支持原始类型 ============
static String classify(int statusCode) {
return switch (statusCode) {
case 200 -> "OK";
case 301 -> "Moved Permanently";
case 404 -> "Not Found";
case 500 -> "Internal Server Error";
case int i when i >= 200 && i < 300 -> "Success: " + i;
case int i when i >= 400 && i < 500 -> "Client Error: " + i;
case int i when i >= 500 -> "Server Error: " + i;
default -> "Unknown: " + statusCode;
};
}
// instanceof + 原始类型
Object value = getMetric();
if (value instanceof int i && i > 0) {
processPositiveInt(i);
} else if (value instanceof double d && d > 0.0) {
processPositiveDouble(d);
} else if (value instanceof long l) {
processLong(l);
}
3.2 Flexible Constructor Bodies(灵活构造器体)¶
// ============ 在super()/this()调用之前执行语句 ============
public class PositiveInteger {
private final int value;
// Java 24之前:不能在super()前执行任何语句
// Java 24+:允许在调用super()之前进行参数校验
public PositiveInteger(int value) {
// ✅ Java 24+: 可以在super()前执行验证逻辑
if (value <= 0) {
throw new IllegalArgumentException("Value must be positive: " + value);
}
super(); // 显式调用
this.value = value;
}
}
// 更实用的场景 — 子类构造器
public class NamedUser extends User {
public NamedUser(String rawName, String email) {
// 在super()前做参数预处理
String sanitized = rawName.strip().toLowerCase();
if (sanitized.isEmpty()) {
throw new IllegalArgumentException("Name cannot be blank");
}
super(sanitized, email); // 传入处理后的参数
}
}
3.3 其他重要特性¶
// ============ Module Import Declarations(JEP 476) ============
// 整个模块的导入,简化大量import
import module java.base; // 导入整个java.base模块的所有公开包
// 等价于:
// import java.util.*;
// import java.io.*;
// import java.time.*;
// import java.math.*;
// ... 几十个包
// ============ Compact Object Headers(JEP 450) ============
// JVM实验特性 — 压缩对象头从128bit到64bit
// 启用方式:-XX:+UseCompactObjectHeaders
// 效果:堆内存消耗降低10-20%
// ============ Late Barrier Expansion for G1(JEP 475) ============
// G1 GC性能优化 — C2编译器延迟屏障扩展
// 效果:减少编译时间5-10%
// ============ Key Derivation Function API(JEP 478) ============
import javax.crypto.KDF;
// 标准化密钥派生函数API
KDF hkdf = KDF.getInstance("HKDF-SHA256");
SecretKey derivedKey = hkdf.deriveKey("AES",
new HKDFParameterSpec.Extract(salt, inputKeyMaterial)
.andExpand(info, 32));
4. GraalVM与Native Image¶
4.1 GraalVM概述¶
GraalVM是Oracle开发的高性能JDK发行版,支持AOT(Ahead-of-Time)编译,可将Java应用编译为独立的本地可执行文件。
GraalVM架构:
┌─────────────────────────────────────────────┐
│ GraalVM │
├──────────┬──────────┬───────────────────────┤
│ JIT编译器 │ Native │ 多语言支持(Truffle) │
│ (Graal) │ Image │ JS/Python/Ruby/R/... │
├──────────┴──────────┴───────────────────────┤
│ JVM (HotSpot) │
└─────────────────────────────────────────────┘
Native Image编译流程:
Java源码 → .class字节码 → Graal AOT编译器 → 本地机器码(可执行文件)
编译时 构建时(静态分析) 运行时
4.2 AOT编译原理¶
JIT(Just-In-Time)vs AOT(Ahead-Of-Time)对比:
┌──────────────┬──────────────────────┬──────────────────────┐
│ 特性 │ JIT(传统) │ AOT(Native) │
├──────────────┼──────────────────────┼──────────────────────┤
│ 编译时机 │ 运行时编译热点代码 │ 构建时全部编译 │
│ 启动时间 │ 慢(需要预热) │ 极快(ms级) │
│ 峰值性能 │ 高(Profile-Guided) │ 中等 │
│ 内存占用 │ 高(JVM + JIT缓存) │ 低(无JVM开销) │
│ 二进制大小 │ 小(JAR)+ JRE │ 大(自包含可执行文件) │
│ 构建时间 │ 快 │ 慢(静态分析耗时) │
│ 动态特性 │ 完全支持 │ 受限(需配置) │
└──────────────┴──────────────────────┴──────────────────────┘
AOT核心步骤:
1. 可达性分析(Points-to Analysis)
- 从main()入口递归分析所有可达代码
- 未被引用的类/方法不会编入最终二进制
2. 初始化分析
- 某些类可以在构建时初始化(Build-Time Init)
- 减少运行时初始化开销
3. 机器码生成
- 将字节码直接编译为目标平台的本机代码
- 包含轻量级GC(Serial / G1)
- 生成独立可执行文件(无需JRE)
4.3 从Spring Boot到Native Image¶
<!-- pom.xml — Spring Boot 3.x原生支持GraalVM -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- GraalVM Native Maven Plugin -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<buildArgs>
<!-- 构建参数 -->
<buildArg>--no-fallback</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
<!-- 启用G1 GC(GraalVM 22.3+) -->
<buildArg>--gc=G1</buildArg>
<!-- 压缩可执行文件 -->
<buildArg>-Ob</buildArg>
</buildArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
# 构建Native Image
# 前提:已安装GraalVM(推荐SDKMAN安装)
sdk install java 22.0.1-graal
sdk use java 22.0.1-graal
# Maven构建Native Image
./mvnw -Pnative native:compile
# Gradle构建
./gradlew nativeCompile
# 构建Docker容器中的Native Image(无需本地安装GraalVM)
./mvnw -Pnative spring-boot:build-image
4.4 反射配置与Resource配置¶
GraalVM的AOT编译需要在构建时确定所有代码路径,动态特性需要额外配置。
// src/main/resources/META-INF/native-image/reflect-config.json
// 反射配置 — 告诉GraalVM哪些类需要反射访问
[
{
"name": "com.example.entity.User",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true
},
{
"name": "com.example.dto.OrderDTO",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
]
// src/main/resources/META-INF/native-image/resource-config.json
// 资源文件配置 — 告诉GraalVM哪些资源文件需要打包
{
"resources": {
"includes": [
{ "pattern": "application\\.yml" },
{ "pattern": "application-.*\\.yml" },
{ "pattern": "templates/.*\\.html" },
{ "pattern": "static/.*" },
{ "pattern": "db/migration/.*\\.sql" }
]
}
}
// src/main/resources/META-INF/native-image/serialization-config.json
// 序列化配置
[
{ "name": "com.example.entity.User" },
{ "name": "com.example.dto.ApiResponse" },
{ "name": "java.util.ArrayList" }
]
/**
* 使用Spring Boot的Runtime Hints注册反射/资源/代理
* 更优雅的方式(替代手写JSON配置)
*/
public class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// 注册反射
hints.reflection()
.registerType(User.class, MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS)
.registerType(OrderDTO.class, MemberCategory.values());
// 注册资源文件
hints.resources()
.registerPattern("templates/*.html")
.registerPattern("static/**");
// 注册JDK代理
hints.proxies()
.registerJdkProxy(MyService.class);
// 注册序列化
hints.serialization()
.registerType(User.class);
}
}
// 注册到Spring Boot
@SpringBootApplication
@ImportRuntimeHints(MyRuntimeHints.class)
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
# 使用GraalVM Tracing Agent自动生成配置
# 1. 用Agent模式运行应用,自动收集反射/资源/代理信息
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
-jar target/my-app.jar
# 2. 运行所有功能(触发所有代码路径),然后停止应用
# 3. Agent会自动生成:reflect-config.json, resource-config.json,
# proxy-config.json, serialization-config.json
# 合并多次运行的配置
java -agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image \
-jar target/my-app.jar
4.5 构建速度优化¶
# ============ 构建速度优化策略 ============
# 1. 使用Quick Build模式(开发阶段)
native-image -Ob my-app.jar # -Ob = 快速构建,牺牲运行时性能
# 2. 增加构建内存
native-image -J-Xmx16g my-app.jar
# 3. 使用构建缓存(增量编译)
native-image --enable-build-cache my-app.jar
# 4. CI/CD中使用Docker构建(隔离环境,利用层缓存)
# Dockerfile.native
FROM ghcr.io/graalvm/native-image:22 AS builder
WORKDIR /app
COPY . .
RUN ./mvnw -Pnative native:compile -DskipTests
FROM debian:bookworm-slim
COPY --from=builder /app/target/my-app /app
EXPOSE 8080
ENTRYPOINT ["/app"]
4.6 内存占用对比(JVM vs Native)¶
实测数据(Spring Boot 3.2 RESTful API):
┌──────────────────┬──────────────────┬──────────────────┐
│ 指标 │ JVM (JIT) │ Native Image │
├──────────────────┼──────────────────┼──────────────────┤
│ 启动时间 │ ~2.5s │ ~0.05s (50ms) │
│ 首次请求延迟 │ ~500ms (冷启动) │ ~5ms │
│ RSS内存占用 │ ~250MB │ ~60MB │
│ 构建时间 │ ~15s (mvn) │ ~3min (native) │
│ 二进制大小 │ ~25MB (JAR) │ ~80MB (独立) │
│ 峰值吞吐量 │ ★★★★★ (JIT优化) │ ★★★☆☆ │
│ P99延迟 │ 偶尔GC抖动 │ 非常稳定 │
└──────────────────┴──────────────────┴──────────────────┘
适用场景:
- Native Image ✅:Serverless/FaaS、CLI工具、微服务(关注启动速度/内存)
- JVM JIT ✅:高吞吐量服务、长时间运行的应用(需要JIT峰值优化)
4.7 GraalVM限制与变通方案¶
// ============ GraalVM Native Image的主要限制 ============
/*
1. 反射(Reflection)
限制:运行时反射调用的类/方法必须预先注册
方案:使用Tracing Agent + reflect-config.json
2. 动态代理(Dynamic Proxy)
限制:运行时创建的代理接口需预先注册
方案:proxy-config.json 或 RuntimeHints
3. 动态类加载
限制:不支持运行时动态加载新类
方案:确保所有类在构建时可达
4. Serialization
限制:可序列化的类需要注册
方案:serialization-config.json
5. JNI
限制:JNI调用需要配置
方案:jni-config.json
6. 某些第三方库不兼容
限制:大量使用反射/字节码增强的库(如Mockito)
方案:查看GraalVM兼容性列表,使用替代库
*/
// Spring Boot 3.x已内置对常见库的Native支持:
// ✅ Spring Web / WebFlux
// ✅ Spring Data JPA / MongoDB / Redis
// ✅ Spring Security
// ✅ Hibernate (需注册实体类)
// ✅ Jackson (JSON序列化)
// ⚠️ MyBatis (需额外配置, mybatis-native可用)
// ❌ CGLIB代理 → 使用JDK代理替代 (spring.aop.proxy-target-class=false)
/**
* 测试Native Image兼容性
* Spring Boot提供 @NativeHint & 测试支持
*/
@SpringBootTest
@DisabledInNativeImage // 某些测试在Native中不可用
class ReflectionTest {
// ...
}
@SpringBootTest
@EnabledInNativeImage // 仅在Native中运行的测试
class NativeSpecificTest {
// ...
}
5. 虚拟线程深入(Loom项目实战)¶
5.1 虚拟线程回顾与深入¶
虚拟线程在Java 21正式发布(JEP 444),是Project Loom的核心成果,彻底改变了Java并发编程模型。
// ============ 虚拟线程原理 ============
/*
传统(平台)线程:
Java Thread ────→ OS Thread ────→ CPU Core
1:1映射,操作系统管理调度
创建成本高(~1MB栈内存),数量受限(通常<10000)
虚拟线程:
Virtual Thread ──→ Carrier Thread (平台线程) ──→ OS Thread ──→ CPU Core
M:N映射,JVM管理调度
创建成本极低(~几KB),数量无限制(百万级别)
关键机制 — Continuation(续体):
当虚拟线程遇到阻塞操作(IO/sleep/锁)时:
1. JVM保存虚拟线程的栈帧(Continuation)→ 堆内存
2. 释放载体线程(Carrier Thread)给其他虚拟线程使用
3. 阻塞操作完成后,调度器将虚拟线程挂载到可用的载体线程上继续执行
→ 结果:阻塞操作不会浪费OS线程!
*/
// ============ 创建虚拟线程的多种方式 ============
// 方式1:Thread.startVirtualThread
Thread vt = Thread.startVirtualThread(() -> {
System.out.println("Hello from virtual thread: " + Thread.currentThread());
});
// 方式2:Thread.ofVirtual() Builder
Thread vt2 = Thread.ofVirtual()
.name("my-vt-", 0) // 命名前缀 + 计数器
.start(() -> doWork());
// 方式3:虚拟线程ExecutorService(推荐)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 每个任务一个虚拟线程 — 不用线程池管理!
for (int i = 0; i < 100_000; i++) {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return fetchData();
});
}
} // try-with-resources自动关闭
5.2 虚拟线程实战:高并发HTTP服务¶
/**
* 使用虚拟线程处理高并发HTTP请求
* Spring Boot 3.2+ 原生支持
*/
// application.yml
// spring.threads.virtual.enabled=true # 一行配置启用虚拟线程
/**
* 手动配置虚拟线程(更细粒度控制)
*/
@Configuration
public class VirtualThreadConfig {
@Bean
public TomcatProtocolHandlerCustomizer<?> virtualThreadCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
/**
* 异步任务也使用虚拟线程
*/
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
}
5.3 虚拟线程注意事项¶
// ============ 注意事项与最佳实践 ============
// ❌ 错误1:不要池化虚拟线程
// 虚拟线程创建成本极低,不需要线程池
ExecutorService bad = Executors.newFixedThreadPool(10); // ❌ 平台线程池
ExecutorService good = Executors.newVirtualThreadPerTaskExecutor(); // ✅ 每任务虚拟线程
// ❌ 错误2:避免在虚拟线程中使用synchronized(会pinning)
class BadExample {
private final Object lock = new Object();
void process() {
synchronized (lock) { // ❌ 会导致载体线程被pinning(无法卸载)
blockingIO();
}
}
}
class GoodExample {
private final ReentrantLock lock = new ReentrantLock();
void process() {
lock.lock(); // ✅ ReentrantLock支持虚拟线程
try {
blockingIO();
} finally {
lock.unlock();
}
}
}
// ❌ 错误3:不要使用ThreadLocal存储大量数据
// 虚拟线程可能有百万个,每个ThreadLocal占用堆内存
// ✅ 改用ScopedValue(Java 23+)
// ❌ 错误4:CPU密集型任务不适合虚拟线程
// 虚拟线程优势在IO密集型场景(网络调用、数据库查询、文件IO)
// CPU密集型任务应该使用平台线程 + ForkJoinPool
// ============ 监控虚拟线程 ============
// JFR(Java Flight Recorder)事件
// jdk.VirtualThreadStart — 虚拟线程启动
// jdk.VirtualThreadEnd — 虚拟线程结束
// jdk.VirtualThreadPinned — 虚拟线程被pinning(需要排查!)
// 启用JFR:
// java -XX:StartFlightRecording=filename=recording.jfr,duration=60s -jar app.jar
// 检测pinning(-Djdk.tracePinnedThreads=full)
// 输出被pinning的虚拟线程堆栈
5.4 虚拟线程性能对比¶
/**
* 性能对比测试:10万并发请求
*/
public class VirtualThreadBenchmark {
private static final int TASK_COUNT = 100_000;
// 平台线程池(200线程)
static long platformThreadTest() throws Exception {
long start = System.currentTimeMillis();
try (var executor = Executors.newFixedThreadPool(200)) {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < TASK_COUNT; i++) {
futures.add(executor.submit(() -> {
Thread.sleep(100); // 模拟IO操作(100ms)
return "done";
}));
}
for (var f : futures) f.get();
}
return System.currentTimeMillis() - start;
}
// 虚拟线程
static long virtualThreadTest() throws Exception {
long start = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < TASK_COUNT; i++) {
futures.add(executor.submit(() -> {
Thread.sleep(100); // 模拟IO操作(100ms)
return "done";
}));
}
for (var f : futures) f.get();
}
return System.currentTimeMillis() - start;
}
public static void main(String[] args) throws Exception {
System.out.println("平台线程(200): " + platformThreadTest() + "ms");
// ~50000ms(100000任务 / 200线程 × 100ms)
System.out.println("虚拟线程: " + virtualThreadTest() + "ms");
// ~200ms(10万虚拟线程并发,全部同时sleep)
}
}
6. Project Panama(FFM API与C互操作)¶
6.1 Foreign Function & Memory API概述¶
Project Panama的FFM API(JEP 454,Java 22正式化)提供安全高效的Java与本地代码(C/C++)互操作能力,替代JNI。
// ============ FFM API vs JNI对比 ============
/*
┌──────────────┬───────────────────────────┬──────────────────────┐
│ 特性 │ JNI(传统) │ FFM API(现代) │
├──────────────┼───────────────────────────┼──────────────────────┤
│ 安全性 │ 不安全(手动内存管理) │ Arena作用域管理 │
│ 编写方式 │ 需要C/C++胶水代码 + .h头文件│ 纯Java代码 │
│ 性能 │ 有JNI开销 │ 接近直接调用 │
│ 调试 │ 困难(跨语言调试) │ Java调试器可用 │
│ 代码生成 │ javah │ jextract工具 │
│ 内存访问 │ 通过JNI函数间接访问 │ MemorySegment直接访问 │
└──────────────┴───────────────────────────┴──────────────────────┘
*/
6.2 调用C标准库函数¶
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
/**
* 使用FFM API调用C标准库函数
*/
public class FFMDemo {
public static void main(String[] args) throws Throwable {
// ===== 示例1:调用strlen (计算字符串长度) =====
// 获取系统链接器
Linker linker = Linker.nativeLinker();
// 查找C标准库中的strlen函数
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(
ValueLayout.JAVA_LONG, // 返回值: size_t (long)
ValueLayout.ADDRESS // 参数: const char* (指针)
)
);
// 使用Arena管理本地内存(自动释放)
try (Arena arena = Arena.ofConfined()) {
// 分配本地内存并写入C字符串
MemorySegment cString = arena.allocateFrom("Hello, Panama!");
// 调用C函数
long length = (long) strlen.invoke(cString);
System.out.println("字符串长度: " + length); // 14
}
// Arena关闭 → 本地内存自动释放,不会内存泄漏
// ===== 示例2:调用printf =====
MethodHandle printf = linker.downcallHandle(
stdlib.find("printf").orElseThrow(),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // 返回值: int
ValueLayout.ADDRESS // 参数: const char* (格式字符串)
),
Linker.Option.firstVariadicArg(1) // 可变参数从第1个开始
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment format = arena.allocateFrom("Hello %s, you are %d years old!\n");
MemorySegment name = arena.allocateFrom("Java");
printf.invoke(format, name, 29);
}
}
}
6.3 操作本机内存(MemorySegment)¶
/**
* 使用MemorySegment高效操作本机内存
* 场景:高性能数据处理(如图像处理、网络缓冲区)
*/
public class MemorySegmentDemo {
// 定义C结构体布局:struct Point { int x; int y; double z; }
static final StructLayout POINT_LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y"),
ValueLayout.JAVA_DOUBLE.withName("z")
);
// 通过VarHandle访问结构体字段
static final VarHandle X_HANDLE = POINT_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("x"));
static final VarHandle Y_HANDLE = POINT_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("y"));
static final VarHandle Z_HANDLE = POINT_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("z"));
public static void main(String[] args) {
// ===== 分配和操作结构体数组 =====
try (Arena arena = Arena.ofConfined()) {
// 分配10个Point结构体的数组
MemorySegment points = arena.allocate(POINT_LAYOUT, 10);
// 设置第0个Point的值
long offset0 = 0;
X_HANDLE.set(points, offset0, 10);
Y_HANDLE.set(points, offset0, 20);
Z_HANDLE.set(points, offset0, 3.14);
// 读取值
int x = (int) X_HANDLE.get(points, offset0);
int y = (int) Y_HANDLE.get(points, offset0);
double z = (double) Z_HANDLE.get(points, offset0);
System.out.printf("Point(0): x=%d, y=%d, z=%.2f%n", x, y, z);
// ===== 批量内存操作 =====
// 分配1GB本机内存作为缓冲区(不占JVM堆)
MemorySegment buffer = arena.allocate(1024 * 1024 * 1024L);
// 高效填充
buffer.fill((byte) 0);
// 内存拷贝
MemorySegment src = arena.allocateFrom("Hello Panama");
MemorySegment dst = arena.allocate(src.byteSize());
MemorySegment.copy(src, 0, dst, 0, src.byteSize());
}
}
}
6.4 加载外部共享库¶
/**
* 加载自定义C共享库(.so/.dll/.dylib)
*/
public class NativeLibDemo {
/*
* 假设有C库 libmath.so:
* double fast_sqrt(double x);
* void matrix_multiply(double* a, double* b, double* result, int n);
*/
public static void main(String[] args) throws Throwable {
// 加载共享库
SymbolLookup mathLib = SymbolLookup.libraryLookup("libmath.so", Arena.global());
Linker linker = Linker.nativeLinker();
// 绑定 fast_sqrt 函数
MethodHandle fastSqrt = linker.downcallHandle(
mathLib.find("fast_sqrt").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
);
double result = (double) fastSqrt.invoke(144.0);
System.out.println("sqrt(144) = " + result); // 12.0
// 绑定 matrix_multiply
MethodHandle matMul = linker.downcallHandle(
mathLib.find("matrix_multiply").orElseThrow(),
FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS, // double* a
ValueLayout.ADDRESS, // double* b
ValueLayout.ADDRESS, // double* result
ValueLayout.JAVA_INT // int n
)
);
try (Arena arena = Arena.ofConfined()) {
int n = 3;
MemorySegment a = arena.allocate(ValueLayout.JAVA_DOUBLE, n * n);
MemorySegment b = arena.allocate(ValueLayout.JAVA_DOUBLE, n * n);
MemorySegment result2 = arena.allocate(ValueLayout.JAVA_DOUBLE, n * n);
// 填充矩阵数据...
matMul.invoke(a, b, result2, n);
}
}
}
6.5 使用jextract自动生成绑定¶
# jextract根据C头文件自动生成Java绑定代码
# 安装jextract(配合GraalVM或单独下载)
# 从C头文件生成Java代码
jextract \
--source \
--output src/main/java \
--target-package com.example.native_bindings \
--header-class-name MathLib \
/usr/include/math.h
# 生成结果可直接在Java中使用:
# MathLib.sqrt(144.0)
# MathLib.sin(Math.PI)
// 使用jextract生成的绑定(示意)
import com.example.native_bindings.MathLib;
public class JextractDemo {
public static void main(String[] args) {
// 直接像Java方法一样调用C函数
double sqrtResult = MathLib.sqrt(144.0);
double sinResult = MathLib.sin(Math.PI / 2);
System.out.printf("sqrt(144)=%.1f, sin(PI/2)=%.1f%n", sqrtResult, sinResult);
}
}
7. 面试题¶
Q1: Java虚拟线程和Go的Goroutine有什么区别?¶
A:
相似点:
- 都是用户态轻量级线程(M:N调度)
- 创建成本低(几KB vs 平台线程~1MB)
- 都支持百万级并发
核心区别:
┌──────────────────┬──────────────────────┬──────────────────────┐
│ 特性 │ Java Virtual Thread │ Go Goroutine │
├──────────────────┼──────────────────────┼──────────────────────┤
│ 调度模型 │ 抢占式(JVM调度) │ 协作式+抢占式 │
│ 栈内存 │ 动态增长(几KB起) │ 动态增长(几KB起) │
│ 阻塞处理 │ 自动卸载Carrier │ 网络自动、syscall独立 │
│ 通信方式 │ 共享内存 + 锁 │ CSP Channel(推荐) │
│ 兼容性 │ 完全兼容Thread API │ 独立goroutine API │
│ synchronized问题 │ 会pinning载体线程 │ 无该问题 │
│ 成熟度 │ Java 21正式(2023) │ Go 1.0即有(2012) │
│ 生态适配 │ 需要库适配 │ 原生全面支持 │
└──────────────────┴──────────────────────┴──────────────────────┘
Java虚拟线程的独特优势:
- 与现有Thread API完全兼容,迁移成本低
- 与Structured Concurrency/ScopedValue协同设计
- 不需要改变编程模型(仍然是线程模型,不是async/await)
Q2: GraalVM Native Image的构建过程和主要限制是什么?在什么场景下应该/不应该使用?¶
A:
构建过程:
1. 可达性分析(Points-to Analysis)
- 从入口点(main)开始静态分析所有可达代码
- 构建调用图(Call Graph),确定运行时需要的类/方法
2. 类初始化策略
- Build-Time Init:在构建时初始化(快,但需要注意副作用)
- Run-Time Init:运行时初始化(兼容性好)
3. AOT编译
- 将字节码编译为目标平台的本地机器码
- 内联轻量级GC(Serial GC / G1)
- 生成独立的可执行文件
主要限制:
1. 反射 → 需要预注册(reflect-config.json)
2. 动态类加载 → 不支持
3. 动态代理 → 需要预注册
4. 某些第三方库不兼容(大量字节码增强的库)
5. 构建时间长(分钟级别)
6. 峰值吞吐量不如JIT
应该使用的场景:
- Serverless / FaaS(冷启动至关重要)
- CLI工具(即开即用)
- 微服务(内存敏感、快速扩缩容)
- 容器化应用(镜像体积小)
不应该使用的场景:
- 高吞吐量长时间运行的服务(JIT峰值性能更好)
- 大量依赖反射的应用(配置成本高)
- 快速迭代开发阶段(构建时间太长)
Q3: Structured Concurrency解决了什么问题?与CompletableFuture相比有何优势?¶
A:
// Structured Concurrency解决的核心问题:线程泄漏 + 错误传播
// ===== CompletableFuture的痛点 =====
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(
() -> userService.getUser(id));
CompletableFuture<List<Order>> ordersFuture = CompletableFuture.supplyAsync(
() -> orderService.getOrders(id));
// 问题1:如果userFuture失败,ordersFuture仍在运行(线程浪费)
// 问题2:异常处理复杂(需要exceptionally/handle链式处理)
// 问题3:取消困难(cancel不一定真正中断)
// 问题4:代码结构与并发结构不一致
// ===== Structured Concurrency的解决方案 =====
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var user = scope.fork(() -> userService.getUser(id));
var orders = scope.fork(() -> orderService.getOrders(id));
scope.join(); // 等待所有子任务
scope.throwIfFailed(); // 任何失败直接抛异常
return new Profile(user.get(), orders.get());
}
// ✅ 任务失败 → 自动取消其他任务
// ✅ 异常自动传播 → 不会悄悄吞掉
// ✅ try-with-resources → 确保所有线程终止
// ✅ 代码结构 = 并发结构 → 可读性强
Q4: Stream Gatherers和传统Stream中间操作有什么区别?什么场景需要自定义Gatherer?¶
A:
核心区别:
- 传统中间操作(map/filter/flatMap):无状态或固定行为,不可扩展
- Gatherer:有状态、可自定义的中间操作,支持N:M的输入输出关系
Gatherer的组成部分:
1. Initializer(初始化器):创建中间状态
2. Integrator(积分器):处理每个元素,决定是否向下游推送
3. Combiner(组合器):并行流中合并状态(可选)
4. Finisher(完成器):流结束时的收尾操作(可选)
需要自定义Gatherer的场景:
- 滑动窗口 / 固定窗口分组
- 去重连续重复元素(类似uniq)
- 有状态的过滤(如"取前N个满足条件的")
- 采样(每N个取1个)
- 批量处理(积攒N个后一次性处理)
- 限速(时间窗口内限制输出数量)
已有的Stream操作仍然更适合:
- 一对一映射 → map()
- 过滤 → filter()
- 一对多展开 → flatMap()
- 去重 → distinct()
- 排序 → sorted()
Q5: FFM API(Project Panama)相比JNI有什么优势?使用时要注意什么?¶
A:
主要优势:
1. 纯Java代码:无需编写C/C++胶水代码和头文件
2. 安全性:Arena管理本地内存,自动释放,防止泄漏
3. 性能:更少的JNI开销,接近直接调用
4. 调试:可以用Java调试器,不需要跨语言调试
5. jextract工具:从C头文件自动生成Java绑定
注意事项:
1. Arena选择:
- Arena.ofConfined():单线程访问(最安全)
- Arena.ofShared():多线程共享
- Arena.global():全局生命周期(慎用,不会自动释放)
- Arena.ofAuto():GC管理(不推荐关键路径)
2. MemorySegment边界检查:
- 访问越界会抛出IndexOutOfBoundsException(比JNI安全)
- 使用后arena关闭会抛出IllegalStateException
3. 结构体对齐:
- 注意C结构体的内存对齐规则
- 使用MemoryLayout.structLayout正确声明padding
4. 线程安全:
- Confined Arena只能在创建它的线程中使用
- 跨线程传递需要Shared Arena
5. 平台兼容性:
- 本地库是平台相关的(.so/.dll/.dylib)
- 需要为每个目标平台编译
Q6: Java 22-25的新特性中,哪些对日常开发影响最大?如何做版本迁移?¶
A:
对日常开发影响最大的特性:
1. 虚拟线程(Java 21正式,持续优化)
影响:彻底改变IO密集型应用的并发模型
迁移:spring.threads.virtual.enabled=true(一行配置)
2. Unnamed Patterns/Variables(Java 22正式)
影响:减少无用变量声明,代码更清晰
迁移:无破坏性,IDE自动提示
3. Structured Concurrency(正式化进程中)
影响:替代CompletableFuture的复杂链式调用
迁移:逐步替换并发代码
4. Stream Gatherers(Java 22预览→正式化)
影响:Stream API可扩展性大幅增强
迁移:新代码直接使用,旧代码按需替换
5. ScopedValue(正式化进程中)
影响:替代ThreadLocal(尤其在虚拟线程场景)
迁移:逐步从ThreadLocal迁移
版本迁移建议:
- Java 8/11 → 21:最优先,21是LTS,生态成熟
- Java 21 → 22+:按需采用预览特性
- 迁移工具:jdeprscan(弃用API检查)、jdeps(依赖分析)
- 先跑完测试套件,再逐步启用新特性
- Spring Boot 3.x 要求 Java 17+,推荐 Java 21
上一章:17-Spring Cloud微服务 - Spring Cloud Alibaba生态、服务治理、分布式事务、微服务部署