Skip to content
<

正式特性

【必备】外部函数和内存 API

长期以来,Java 程序员想要调用 C/C++ 编写的本地库,只能依赖 JNI(Java Native Interface)。但说实话,JNI 的使用体验并不好,需要手写胶水代码、维护头文件和构建脚本、处理 JNIEnv 和复杂类型转换,一旦接口频繁变更,维护成本较高。

外部函数与内存 API(FFM API)提供了标准化、类型安全的⁢方式来从 Java 直接调用本地代码,并在⁡ Java 侧描述函数签名和内存布局。比起⁡ JNI,FFM 一般不需要编写 JNI 胶水代码,调用链更简洁、可维护性更好。

让我们看看用 FFM API 是怎么做的:

java
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class ModernNativeCall {
    public static void main(String[] args) throws Throwable {
        // 获取本地链接器和符号查找器
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdlib = linker.defaultLookup();

        // 直接调用系统已有的C标准库函数,无需自己编写C代码
        MethodHandle sqrt = linker.downcallHandle(
            stdlib.find("sqrt").orElseThrow(() -> 
                new RuntimeException("sqrt函数未找到")),
            FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
        );
        double result = (double) sqrt.invoke(16.0);
        System.out.println("sqrt(16) = " + result);

        // 如果要调用自定义库,也只需要一个Java文件
        try (Arena arena = Arena.ofConfined()) {
            SymbolLookup customLib = SymbolLookup.libraryLookup("mycalculator", arena);
            MethodHandle add = linker.downcallHandle(
                customLib.find("add").orElseThrow(() -> 
                    new RuntimeException("add函数未找到")),
                FunctionDescriptor.of(ValueLayout.JAVA_INT, 
                                      ValueLayout.JAVA_INT, 
                                      ValueLayout.JAVA_INT)
            );
            int sum = (int) add.invoke(10, 20);
            System.out.println("10 + 20 = " + sum);
        } // 库资源在这里自动释放
    }
}

这里有几个关键概念需要理解:

  • Linker 负责连接 Java 代码和本地代码
  • SymbolLookup 用来查找本地库中的函数
  • MethodHandle 是 Java 中已有的概念,表示本地函数的调用

要特别注意的是 Arena.ofConfined(),这是 FFM API 中一个非常重要的内存管理概念。Arena 可以理解为一个内存管理区域,它控制着内存的生命周期。当你使用 try-with-resources 语句时,Arena 会在代码块结束时自动释放所有相关的内存资源,这样就避免了内存泄漏的问题。

FFM API 还有一个强大的特性是 支持双向调用。不仅可以从 Java 调用 C 函数,还可以把 Java 方法作为函数指针传递给 C 函数。比如你可以用 Java 写一个比较器,然后传给 C 标准库的 qsort 函数:

java
public class QsortExample {
    // Java比较器方法
    static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
        return Integer.compare(
            elem1.get(ValueLayout.JAVA_INT, 0), 
            elem2.get(ValueLayout.JAVA_INT, 0)
        );
    }

    public static void main(String[] args) throws Throwable {
        Linker linker = Linker.nativeLinker();
        // 找到qsort函数
        MethodHandle qsort = linker.downcallHandle(
            linker.defaultLookup().find("qsort").orElseThrow(),
            FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, 
                                      ValueLayout.JAVA_LONG, 
                                      ValueLayout.JAVA_LONG, 
                                      ValueLayout.ADDRESS)
        );
        // 把Java方法包装成函数指针
        MethodHandle comparHandle = MethodHandles.lookup()
        .findStatic(QsortExample.class, "qsortCompare",
                    MethodType.methodType(int.class, 
                                          MemorySegment.class, 
                                          MemorySegment.class));

        MemorySegment comparFunc = linker.upcallStub(
            comparHandle,
            FunctionDescriptor.of(ValueLayout.JAVA_INT,
                                  ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
                                  ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT)),
            Arena.ofAuto()
        );

        // 使用qsort排序数组
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment array = arena.allocateFrom(
                ValueLayout.JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7);
            qsort.invoke(array, 10L, ValueLayout.JAVA_INT.byteSize(), comparFunc);
            int[] sorted = array.toArray(ValueLayout.JAVA_INT);
            System.out.println(java.util.Arrays.toString(sorted));
            // 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        }
    }
}

FFM API 现在支持几乎所⁢有主流平台,性能相比⁡ JNI 可能有一定⁡提升,特别是在频繁调用本地函数的场景下。

内存管理示例

java
import java.lang.foreign.*;

public class MemoryManagementExample {
    public static void main(String[] args) {
        // 使用 Arena 管理内存
        try (Arena arena = Arena.ofConfined()) {
            // 分配本地内存
            MemorySegment segment = arena.allocate(1024);
            
            // 写入数据
            segment.setAtIndex(ValueLayout.JAVA_INT, 0, 42);
            segment.setAtIndex(ValueLayout.JAVA_INT, 1, 24);
            
            // 读取数据
            int value1 = segment.getAtIndex(ValueLayout.JAVA_INT, 0);
            int value2 = segment.getAtIndex(ValueLayout.JAVA_INT, 1);
            
            System.out.println("值1: " + value1 + ", 值2: " + value2);
            
            // 分配字符串
            MemorySegment cString = arena.allocateFrom("Hello FFM API!");
            System.out.println("C字符串长度: " + cString.byteSize());
            
        } // Arena 自动释放所有内存
    }
}

【了解】未命名变量和模式

在开发中,我们可能会遇到这样⁢的情况:有些变量我⁡们必须声明,但实际⁡上并不会使用到它们的值。

在这之前,我们只能给这些不使⁢用的变量起一个名字⁡,代码会显得有⁡些多余。举些例子:

java
try {
    processData();
} catch (IOException ignored) {  // 只关心异常发生,不关心异常对象
    System.out.println("处理数据时出错了");
}

String result = switch (obj) {
    case Integer i -> "这是整数: " + i;
    case String s -> "这是字符串: " + s;
    case Double unused -> "这是浮点数";  // 不需要使用具体的值
    default -> "未知类型";
};

有了未命名变量特性,可以使用下划线 _ 表示不使用的变量代码,意图更清晰:

java
try {
    processData();
} catch (IOException _) { // 不关心异常对象
    System.out.println("处理数据时出错了");
}

String result = switch (obj) {
    case Integer i -> "这是整数: " + i;
    case String s -> "这是字符串: " + s;
    case Double _ -> "这是浮点数";  // 只关心类型,不关心值
    default -> "未知类型";
};

// 在解构中也很有用
if (point instanceof Point(var x, var _)) {  // 只关心 x 坐标
    System.out.println("x 坐标是: " + x);
}

未命名变量的使用场景

java
public class UnnamedVariableExample {
    public static void main(String[] args) {
        // 在 for 循环中
        for (int i = 0, _ = sideEffect(); i < 10; i++) {
            System.out.println("循环 " + i);
        }
        
        // 在多重赋值中
        var (result, _) = processData(); // 只关心第一个返回值
        
        // 在 lambda 表达式中
        list.forEach(_ -> System.out.println("处理一个元素"));
        
        // 在 try-with-resources 中
        try (var _ = acquireResource()) {
            // 只需要资源被正确关闭,不关心资源对象本身
            doWork();
        }
    }
    
    private static int sideEffect() {
        System.out.println("副作用");
        return 42;
    }
    
    private static String processData() {
        return "result";
    }
    
    private static AutoCloseable acquireResource() {
        return () -> System.out.println("资源已关闭");
    }
    
    private static void doWork() {
        System.out.println("执行工作");
    }
}

【了解】G1 的区域固定

Java 22 改进了 G1 垃圾⁢收集器,引入了区域固定⁡功能:

bash
# 启用 G1 垃圾收集器
java -XX:+UseG1GC MyApp

# G1 现在可以固定某些区域,避免在 GC 期间移动
# 这对于与本地代码交互的应用特别有用

这个改进主要影响:

  • 本地内存交互:减少 GC 对本地代码的影响
  • 大对象处理:改善大对象的 GC 性能
  • 延迟优化:减少某些 GC 暂停

【了解】启动多文件源代码程序

Java 22 扩展了单文件⁢源代码程序的功能,⁡现在支持多文件:

bash
# 可以直接运行多文件的 Java 程序
java Main.java

# 或者指定主类
java --main-class com.example.Main *.java

这个特性让原型开发和脚本编写⁢更加方便:    ⁡         ⁡

java
// Main.java
import com.example.Utils;

public class Main {
    public static void main(String[] args) {
        Utils.printMessage("Hello Multi-file!");
    }
}

// com/example/Utils.java
package com.example;

public class Utils {
    public static void printMessage(String message) {
        System.out.println("消息: " + message);
    }
}

预览特性

【了解】super(...) 之前的语句(预览)

Java 22 允许在调用 super(...) 之前执行一些语句:

java
// 需要启用预览功能
// javac --enable-preview --release 22 Example.java

public class Child extends Parent {
    private final String processedValue;
    
    public Child(String rawValue) {
        // Java 22 之前,这里不能有任何语句
        String processed = processRawValue(rawValue); // 现在可以了!
        super(processed);
        this.processedValue = processed;
    }
    
    private static String processRawValue(String raw) {
        return raw.toUpperCase().trim();
    }
}

class Parent {
    private final String value;
    
    public Parent(String value) {
        this.value = value;
    }
}

这个特性解决了构造函数中参数预处理的问题:

java
public class DatabaseConnection extends Connection {
    public DatabaseConnection(String url, Properties props) {
        // 预处理配置
        Properties processedProps = new Properties(props);
        processedProps.setProperty("useSSL", "true");
        
        // 验证 URL
        if (!isValidUrl(url)) {
            throw new IllegalArgumentException("无效的数据库 URL");
        }
        
        String normalizedUrl = normalizeUrl(url);
        
        // 现在调用父类构造函数
        super(normalizedUrl, processedProps);
    }
    
    private static boolean isValidUrl(String url) {
        return url != null && url.startsWith("jdbc:");
    }
    
    private static String normalizeUrl(String url) {
        return url.toLowerCase();
    }
}

【了解】类文件 API(预览)

Java 22 引入了类文件⁢ API 作为预览⁡特性,提供了读取、⁡分析和生成 Java 类文件的标准方法:

java
import java.lang.classfile.*;
import java.lang.constant.ClassDesc;

// 需要启用预览功能
public class ClassFileExample {
    public static void main(String[] args) throws Exception {
        // 读取现有的类文件
        ClassModel classModel = ClassFile.of().parse(
            ClassFileExample.class.getResourceAsStream("Example.class").readAllBytes()
        );
        
        // 分析类信息
        System.out.println("类名: " + classModel.thisClass().asInternalName());
        System.out.println("父类: " + classModel.superclass().orElse(null));
        
        // 列出方法
        classModel.methods().forEach(method -> {
            System.out.println("方法: " + method.methodName().stringValue());
        });
        
        // 生成新的类文件
        byte[] newClassBytes = ClassFile.of().build(
            ClassDesc.of("com.example.Generated"),
            classBuilder -> {
                classBuilder.withMethod(
                    "hello",
                    MethodTypeDesc.of(ConstantDescs.CD_void),
                    ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
                    methodBuilder -> {
                        methodBuilder.withCode(codeBuilder -> {
                            codeBuilder.getstatic(
                                ClassDesc.of("java.lang.System"),
                                "out",
                                ClassDesc.of("java.io.PrintStream")
                            );
                            codeBuilder.ldc("Hello from generated class!");
                            codeBuilder.invokevirtual(
                                ClassDesc.of("java.io.PrintStream"),
                                "println",
                                MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String)
                            );
                            codeBuilder.return_();
                        });
                    }
                );
            }
        );
        
        // 保存生成的类文件
        java.nio.file.Files.write(
            java.nio.file.Paths.get("Generated.class"),
            newClassBytes
        );
    }
}

【了解】字符串模板(第二次预览)

Java 22 继续完善字符串模板特性:

java
// 需要启用预览功能
public class StringTemplateExample {
    public static void main(String[] args) {
        String name = "张三";
        int age = 25;
        double salary = 15000.50;
        
        // 基本字符串模板
        String basic = STR."姓名: \{name}, 年龄: \{age}";
        System.out.println(basic);
        
        // 格式化模板
        String formatted = FMT."姓名: %-10s\{name} 年龄: %3d\{age} 工资: %,.2f\{salary}";
        System.out.println(formatted);
        
        // 自定义模板处理器
        String json = JSON."""
            {
                "name": "\{name}",
                "age": \{age},
                "salary": \{salary}
            }
            """;
        System.out.println(json);
        
        // 多行模板
        String html = STR."""
            <html>
                <body>
                    <h1>员工信息</h1>
                    <p>姓名: \{name}</p>
                    <p>年龄: \{age}</p>
                    <p>工资: \{salary}</p>
                </body>
            </html>
            """;
        System.out.println(html);
    }
    
    // 自定义模板处理器
    public static final StringTemplate.Processor<String, RuntimeException> JSON = 
        StringTemplate.Processor.of((StringTemplate st) -> {
            StringBuilder sb = new StringBuilder();
            Iterator<String> fragments = st.fragments().iterator();
            Iterator<Object> values = st.values().iterator();
            
            while (fragments.hasNext()) {
                sb.append(fragments.next());
                if (values.hasNext()) {
                    Object value = values.next();
                    if (value instanceof String) {
                        sb.append('"').append(value).append('"');
                    } else {
                        sb.append(value);
                    }
                }
            }
            return sb.toString();
        });
}

【了解】Stream Gatherers(预览)

Java 22 引入了 St⁢ream Gath⁡erers 作为预⁡览特性:

java
import java.util.stream.Gatherer;
import java.util.stream.Gatherers;

// 需要启用预览功能
public class GatherersExample {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 使用内置的 Gatherers
        
        // 1. 滑动窗口
        List<List<Integer>> windows = numbers.stream()
            .gather(Gatherers.windowSliding(3))
            .toList();
        System.out.println("滑动窗口: " + windows);
        // [[1, 2, 3], [2, 3, 4], [3, 4, 5], ...]
        
        // 2. 固定窗口
        List<List<Integer>> fixedWindows = numbers.stream()
            .gather(Gatherers.windowFixed(3))
            .toList();
        System.out.println("固定窗口: " + fixedWindows);
        // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
        
        // 3. 扫描(累积操作)
        List<Integer> scan = numbers.stream()
            .gather(Gatherers.scan(() -> 0, Integer::sum))
            .toList();
        System.out.println("扫描求和: " + scan);
        // [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
        
        // 自定义 Gatherer
        Gatherer<String, ?, String> addPrefix = Gatherer.ofSequential(
            () -> null,  // 初始状态
            (state, element, downstream) -> {
                downstream.push("前缀-" + element);
                return true;
            }
        );
        
        List<String> words = List.of("apple", "banana", "cherry");
        List<String> prefixed = words.stream()
            .gather(addPrefix)
            .toList();
        System.out.println("添加前缀: " + prefixed);
        // [前缀-apple, 前缀-banana, 前缀-cherry]
    }
}

【了解】结构化并发(第二次预览)

Java 22 继续完善结构化并发:

java
import jdk.incubator.concurrent.StructuredTaskScope;

// 需要启用预览功能和孵化器模块
public class StructuredConcurrencyExample {
    public static void main(String[] args) {
        UserProfile profile = fetchUserProfile(123);
        System.out.println(profile);
    }
    
    record UserProfile(User user, Settings settings, List<Notification> notifications) {}
    record User(String name, String email) {}
    record Settings(String theme, String language) {}
    record Notification(String message, String type) {}
    
    public static UserProfile fetchUserProfile(int userId) {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            // 并发执行多个任务
            var userTask = scope.fork(() -> fetchUser(userId));
            var settingsTask = scope.fork(() -> fetchSettings(userId));
            var notificationsTask = scope.fork(() -> fetchNotifications(userId));
            
            // 等待所有任务完成
            scope.join();
            scope.throwIfFailed();
            
            // 组合结果
            return new UserProfile(
                userTask.resultNow(),
                settingsTask.resultNow(),
                notificationsTask.resultNow()
            );
            
        } catch (Exception e) {
            throw new RuntimeException("获取用户资料失败", e);
        }
    }
    
    private static User fetchUser(int userId) {
        // 模拟数据库查询
        simulateDelay(100);
        return new User("用户" + userId, "user" + userId + "@example.com");
    }
    
    private static Settings fetchSettings(int userId) {
        simulateDelay(150);
        return new Settings("dark", "zh-CN");
    }
    
    private static List<Notification> fetchNotifications(int userId) {
        simulateDelay(80);
        return List.of(
            new Notification("欢迎回来!", "info"),
            new Notification("您有新消息", "alert")
        );
    }
    
    private static void simulateDelay(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

孵化器特性

【了解】向量 API(第七次孵化器)

Java 22 继续改进向量⁢ API:    ⁡         ⁡

java
import jdk.incubator.vector.*;

public class VectorAPIExample {
    private static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
    
    public static void main(String[] args) {
        // 向量化的数组操作
        float[] a = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
        float[] b = {8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f};
        float[] result = new float[a.length];
        
        // 向量化加法
        vectorAdd(a, b, result);
        System.out.println("向量加法: " + Arrays.toString(result));
        
        // 向量化点积
        float dotProduct = vectorDotProduct(a, b);
        System.out.println("点积: " + dotProduct);
    }
    
    public static void vectorAdd(float[] a, float[] b, float[] result) {
        int upperBound = SPECIES.loopBound(a.length);
        
        // 向量化循环
        for (int i = 0; i < upperBound; i += SPECIES.length()) {
            FloatVector va = FloatVector.fromArray(SPECIES, a, i);
            FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
            FloatVector vr = va.add(vb);
            vr.intoArray(result, i);
        }
        
        // 处理剩余元素
        for (int i = upperBound; i < a.length; i++) {
            result[i] = a[i] + b[i];
        }
    }
    
    public static float vectorDotProduct(float[] a, float[] b) {
        FloatVector sum = FloatVector.zero(SPECIES);
        int upperBound = SPECIES.loopBound(a.length);
        
        for (int i = 0; i < upperBound; i += SPECIES.length()) {
            FloatVector va = FloatVector.fromArray(SPECIES, a, i);
            FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
            sum = va.fma(vb, sum);
        }
        
        float result = sum.reduceLanes(VectorOperators.ADD);
        
        // 处理剩余元素
        for (int i = upperBound; i < a.length; i++) {
            result += a[i] * b[i];
        }
        
        return result;
    }
}

总结

Java 22 最重要的特性是外部函数和内存 ⁢API 的正式化,这为 Jav⁡a 与本地代码的交互提供了现代⁡化的解决方案。未命名变量和模式让代码更加简洁和表达力更强。

虽然大部分新特性还处于预览阶段,但字符串模板、Stream⁢ Gatherers 等特性都展现出了⁡巨大的潜力。类文件 API 为字节码操⁡作提供了标准化的方法,结构化并发继续完善 Java 的并发编程模型。

Java 22 虽然不是 LT⁢S 版本,但它为未来⁡的重要特性奠定了基础⁡,特别是在性能优化和开发体验方面做出了重要贡献。