跳转至

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互操作

GraalVM Native Image构建流程图


📖 目录

  1. Java 22新特性
  2. Java 23新特性
  3. Java 24-25新特性
  4. GraalVM与Native Image
  5. 虚拟线程深入(Loom项目实战)
  6. Project Panama(FFM API与C互操作)
  7. 面试题

1. Java 22新特性

Java 22(2024年3月发布)带来了多项重要特性从预览走向正式。

1.1 Unnamed Patterns and Variables(JEP 456,正式)

未命名模式和变量允许使用 _ 表示"不关心"的变量,减少无用变量声明。

Java
// ============ 旧写法:被迫声明但不使用的变量 ============
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中间操作不可扩展的短板。

Java
// ============ 内置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();
Java
// ============ 自定义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)。

Java
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)STRFMT 模板处理器和 StringTemplate API 已从 Java 23 及后续版本中移除,以下代码无法在 Java 23+ 上编译。仅作历史参考。

Java
// ⚠️ 以下代码仅适用于 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的现代替代方案,专为虚拟线程设计,具有不可变性和自动清理特性。

Java
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(向正式化推进)

结构化并发确保并发任务的生命周期与代码结构一致,避免线程泄漏。

Java
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(原始类型模式匹配)

Java
// ============ 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(灵活构造器体)

Java
// ============ 在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 其他重要特性

Java
// ============ 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应用编译为独立的本地可执行文件。

Text Only
GraalVM架构:
┌─────────────────────────────────────────────┐
│                 GraalVM                      │
├──────────┬──────────┬───────────────────────┤
│ JIT编译器 │ Native   │ 多语言支持(Truffle)  │
│ (Graal)  │ Image    │ JS/Python/Ruby/R/...  │
├──────────┴──────────┴───────────────────────┤
│              JVM (HotSpot)                   │
└─────────────────────────────────────────────┘

Native Image编译流程:
Java源码 → .class字节码 → Graal AOT编译器 → 本地机器码(可执行文件)
     编译时                     构建时(静态分析)       运行时

4.2 AOT编译原理

Text Only
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

XML
<!-- 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>
Bash
# 构建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编译需要在构建时确定所有代码路径,动态特性需要额外配置。

Text Only
// 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
  }
]
Text Only
// 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" }
    ]
  }
}
Text Only
// src/main/resources/META-INF/native-image/serialization-config.json
// 序列化配置
[
  { "name": "com.example.entity.User" },
  { "name": "com.example.dto.ApiResponse" },
  { "name": "java.util.ArrayList" }
]
Java
/**
 * 使用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);
    }
}
Bash
# 使用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 构建速度优化

Bash
# ============ 构建速度优化策略 ============

# 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)

Text Only
实测数据(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限制与变通方案

Java
// ============ 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
// ============ 虚拟线程原理 ============
/*
传统(平台)线程:
  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服务

Java
/**
 * 使用虚拟线程处理高并发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 虚拟线程注意事项

Java
// ============ 注意事项与最佳实践 ============

// ❌ 错误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 虚拟线程性能对比

Java
/**
 * 性能对比测试: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。

Java
// ============ FFM API vs JNI对比 ============
/*
┌──────────────┬───────────────────────────┬──────────────────────┐
│     特性     │      JNI(传统)           │   FFM API(现代)    │
├──────────────┼───────────────────────────┼──────────────────────┤
│ 安全性       │ 不安全(手动内存管理)     │ Arena作用域管理       │
│ 编写方式     │ 需要C/C++胶水代码 + .h头文件│ 纯Java代码           │
│ 性能         │ 有JNI开销                 │ 接近直接调用          │
│ 调试         │ 困难(跨语言调试)         │ Java调试器可用        │
│ 代码生成     │ javah                     │ jextract工具         │
│ 内存访问     │ 通过JNI函数间接访问        │ MemorySegment直接访问 │
└──────────────┴───────────────────────────┴──────────────────────┘
*/

6.2 调用C标准库函数

Java
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)

Java
/**
 * 使用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 加载外部共享库

Java
/**
 * 加载自定义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自动生成绑定

Bash
# 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)
Java
// 使用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:

Text Only
相似点:
- 都是用户态轻量级线程(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:

Text Only
构建过程:
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:

Java
// 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:

Text Only
核心区别:
- 传统中间操作(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:

Text Only
主要优势:
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:

Text Only
对日常开发影响最大的特性:

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生态、服务治理、分布式事务、微服务部署