欢迎光临
我们一直在努力

Java深入解析篇四十四之Project Panama详解

Project Panama(Foreign Function & Memory API)详解


目录

  • Project Panama概述与动机
  • JNI的问题
  • Foreign Function & Memory API
  • MemorySegment(堆外内存管理)
  • Arena(生命周期管理)
  • MemoryLayout(结构体布局)
  • Linker(本地函数链接)
  • FunctionDescriptor(函数签名描述)
  • MethodHandle调用本地函数
  • 调用C标准库实战
  • 调用自定义本地库
  • 回调(Upcall Stub)
  • 与JNI性能对比
  • jextract工具
  • 最佳实践

  • 一、Project Panama概述与动机

    1.1 什么是Project Panama

    Project Panama 是 OpenJDK 的一个子项目,旨在改善 JVM 与非 Java 代码之间的互操作性。其核心成果是 Foreign Function & Memory API(外部函数与内存API),于 JDK 22 正式发布(JEP 454)。

    ┌─────────────────────────────────────────────────────┐
    │ Java Application │
    ├─────────────────────────────────────────────────────┤
    │ Foreign Function & Memory API │
    │ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
    │ │ Arena │ │ Memory │ │ Linker │ │
    │ │ │ │ Segment │ │ (Downcall/Upcall)│ │
    │ └──────────┘ └──────────┘ └───────────────────┘ │
    ├─────────────────────────────────────────────────────┤
    │ Native Code (C/C++/Rust) │
    │ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
    │ │ libc │ │ libm │ │ custom .so/.dll │ │
    │ └──────────┘ └──────────┘ └───────────────────┘ │
    └─────────────────────────────────────────────────────┘

    1.2 演进历程

    阶段JDK版本JEP状态
    Incubator 1 JDK 14 JEP 370 孵化
    Incubator 2 JDK 15 JEP 383 孵化
    Incubator 3 JDK 16 JEP 393 孵化
    Incubator 4 JDK 17 JEP 412 孵化
    Preview 1 JDK 19 JEP 424 预览
    Preview 2 JDK 20 JEP 434 预览
    Preview 3 JDK 21 JEP 442 预览
    Final JDK 22 JEP 454 正式

    1.3 核心设计目标

    • 易用性:纯Java API,无需编写C/C++胶水代码
    • 安全性:默认启用边界检查,内存生命周期可控
    • 性能:接近JNI甚至超越,支持JIT优化
    • 通用性:支持任意本地库,不局限于特定平台

    1.4 模块与包

    // 所需模块(JDK 22+默认可用)
    // java.base 模块中的 java.lang.foreign 包

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


    二、JNI的问题

    2.1 开发复杂度高

    // JNI方式:需要编写C胶水代码
    // 1. 先定义Java类
    // public class Native { public native int add(int a, int b); }

    // 2. 生成头文件:javac -h . Native.java
    // 3. 实现C代码:
    #include "Native.h"

    JNIEXPORT jint JNICALL Java_Native_add
    (JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b;
    }

    // 4. 编译为动态库
    // gcc -shared -fPIC -o libnative.so -I$JAVA_HOME/include -I$JAVA_HOME/include/linux Native.c

    // JNI方式:Java端加载
    public class Native {
    static {
    System.loadLibrary("native"); // 依赖java.library.path
    }
    public native int add(int a, int b);
    }

    2.2 安全性问题

    // JNI中的危险操作——无任何保护
    JNIEXPORT void JNICALL Java_Native_process
    (JNIEnv *env, jobject obj, jlong ptr, jint len) {
    char *buffer = (char *)ptr;
    // 没有边界检查!可能缓冲区溢出
    buffer[len + 100] = '\\0'; // 越界写入 → 段错误 → JVM崩溃

    // 内存泄漏:忘记释放
    char *leaked = (char *)malloc(1024);
    // 没有free → 永久泄漏
    }

    2.3 性能问题

    // JNI调用开销分析
    public class JniOverhead {
    // 每次JNI调用的隐性成本:
    // 1. 线程状态转换(Java → Native):~10ns
    // 2. 参数类型转换(jstring → char*):~20ns
    // 3. 异常检查:~5ns
    // 4. 阻止JIT内联优化
    // 总计:单次调用 ~50-100ns

    // 大数组传输需要复制
    public native void processArray(byte[] data);
    // C端:(*env)->GetByteArrayElements → 复制整个数组
    // 处理完后:(*env)->ReleaseByteArrayElements → 再次复制
    }

    2.4 Panama的解决方案对比

    问题JNIPanama
    胶水代码 需要C/C++ 纯Java
    内存安全 无保护 边界检查+Arena
    调用开销 ~50-100ns ~5-10ns
    数据传输 需要复制 零拷贝
    调试 跨语言困难 纯Java调试
    工具支持 有限 jextract自动生成

    三、Foreign Function & Memory API

    3.1 API核心架构

    java.lang.foreign
    ├── MemorySegment → 内存段(数据载体)
    ├── Arena → 生命周期管理器
    ├── MemoryLayout → 内存布局描述
    │ ├── ValueLayout → 基本类型布局
    │ ├── StructLayout → 结构体布局
    │ ├── UnionLayout → 联合体布局
    │ ├── SequenceLayout→ 数组布局
    │ └── PaddingLayout → 填充布局
    ├── Linker → 本地函数链接器
    ├── FunctionDescriptor→ 函数签名
    ├── SymbolLookup → 符号查找
    └── ValueLayout → 值布局常量
    ├── JAVA_BYTE/SHORT/INT/LONG
    ├── JAVA_FLOAT/DOUBLE
    ├── JAVA_CHAR
    └── ADDRESS

    3.2 基本使用流程

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

    public class PanamaHello {
    public static void main(String[] args) throws Throwable {
    // 1. 获取链接器
    Linker linker = Linker.nativeLinker();

    // 2. 查找C标准库函数
    SymbolLookup lookup = linker.defaultLookup();
    MemorySegment strlenAddr = lookup.find("strlen").orElseThrow();

    // 3. 描述函数签名:size_t strlen(const char *s)
    FunctionDescriptor desc = FunctionDescriptor.of(
    ValueLayout.JAVA_LONG, // 返回值:size_t
    ValueLayout.ADDRESS // 参数:const char*
    );

    // 4. 创建MethodHandle
    MethodHandle strlen = linker.downcallHandle(strlenAddr, desc);

    // 5. 准备参数并调用
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment cString = arena.allocateFrom("Hello, Panama!");
    long length = (long) strlen.invokeExact(cString);
    System.out.println("Length: " + length); // 14
    }
    }
    }

    3.3 编译与运行

    # JDK 22+ 直接编译运行(无需额外参数)
    javac PanamaHello.java
    java PanamaHello

    # JDK 19-21(Preview阶段需要)
    javac –enable-preview –release 21 PanamaHello.java
    java –enable-preview –enable-native-access=ALL-UNNAMED PanamaHello


    四、MemorySegment(堆外内存管理)

    4.1 基本概念

    MemorySegment 是 Foreign Memory API 的核心,表示一段连续的、有边界的内存区域。它可以指向堆外内存(native memory)或包装Java数组。

    import java.lang.foreign.*;

    public class MemorySegmentDemo {
    public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
    // 分配100字节的堆外内存
    MemorySegment segment = arena.allocate(100);

    // 基本属性
    System.out.println("地址: " + segment.address());
    System.out.println("大小: " + segment.byteSize());
    System.out.println("是否本地: " + segment.isNative());
    }
    // Arena关闭后,segment自动失效,再访问抛IllegalStateException
    }
    }

    4.2 读写基本类型

    public class SegmentReadWrite {
    public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment seg = arena.allocate(64);

    // 写入各种类型(指定偏移量)
    seg.set(ValueLayout.JAVA_INT, 0, 42); // int at offset 0
    seg.set(ValueLayout.JAVA_LONG, 8, 123456789L); // long at offset 8
    seg.set(ValueLayout.JAVA_DOUBLE, 16, 3.14159); // double at offset 16
    seg.set(ValueLayout.JAVA_BYTE, 24, (byte) 0xFF); // byte at offset 24
    seg.set(ValueLayout.JAVA_CHAR, 26, 'A'); // char at offset 26

    // 读取
    int intVal = seg.get(ValueLayout.JAVA_INT, 0); // 42
    long longVal = seg.get(ValueLayout.JAVA_LONG, 8); // 123456789
    double dblVal = seg.get(ValueLayout.JAVA_DOUBLE, 16); // 3.14159
    byte byteVal = seg.get(ValueLayout.JAVA_BYTE, 24); // -1 (0xFF)
    char charVal = seg.get(ValueLayout.JAVA_CHAR, 26); // 'A'

    System.out.printf("int=%d, long=%d, double=%.5f, byte=%d, char=%c%n",
    intVal, longVal, dblVal, byteVal, charVal);
    }
    }
    }

    4.3 字符串操作

    public class SegmentString {
    public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
    // 从Java字符串创建C字符串(自动添加'\\0')
    MemorySegment cStr = arena.allocateFrom("Hello, World!");
    System.out.println("C字符串大小: " + cStr.byteSize()); // 14 (含\\0)

    // 读取C字符串
    String javaStr = cStr.getString(0);
    System.out.println("读回: " + javaStr); // Hello, World!

    // 指定字符集
    MemorySegment utf16Str = arena.allocateFrom(
    java.nio.charset.StandardCharsets.UTF_16, "你好");
    String chinese = utf16Str.getString(0, java.nio.charset.StandardCharsets.UTF_16);
    System.out.println("中文: " + chinese); // 你好
    }
    }
    }

    4.4 数组操作

    public class SegmentArray {
    public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
    // 分配int数组(10个元素)
    MemorySegment intArray = arena.allocateArray(
    ValueLayout.JAVA_INT, 10);

    // 按索引写入
    for (int i = 0; i < 10; i++) {
    intArray.setAtIndex(ValueLayout.JAVA_INT, i, i * i);
    }

    // 按索引读取
    for (int i = 0; i < 10; i++) {
    int val = intArray.getAtIndex(ValueLayout.JAVA_INT, i);
    System.out.printf("[%d] = %d%n", i, val);
    }

    // 转为Java数组
    int[] javaArray = intArray.toArray(ValueLayout.JAVA_INT);
    System.out.println("Java数组: " + java.util.Arrays.toString(javaArray));

    // 从Java数组创建MemorySegment(零拷贝视图)
    int[] source = {1, 2, 3, 4, 5};
    MemorySegment wrapped = MemorySegment.ofArray(source);
    System.out.println("包装大小: " + wrapped.byteSize()); // 20 bytes
    }
    }
    }

    4.5 切片与指针

    public class SegmentSlice {
    public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment buffer = arena.allocate(1024);

    // 创建子切片
    MemorySegment header = buffer.asSlice(0, 16); // 前16字节
    MemorySegment body = buffer.asSlice(16, 256); // 16~271字节

    header.set(ValueLayout.JAVA_INT, 0, 0xDEADBEEF);
    body.set(ValueLayout.JAVA_INT, 0, 42);

    // 指针操作:存储和读取地址
    MemorySegment ptrSlot = arena.allocate(ValueLayout.ADDRESS);
    ptrSlot.set(ValueLayout.ADDRESS, 0, buffer); // 存储buffer的地址

    // 读取指针指向的段
    MemorySegment dereferenced = ptrSlot.get(ValueLayout.ADDRESS, 0, 1024);
    int magic = dereferenced.get(ValueLayout.JAVA_INT, 0);
    System.out.printf("Magic: 0x%08X%n", magic); // 0xDEADBEEF

    // NULL指针
    MemorySegment nullPtr = MemorySegment.NULL;
    System.out.println("NULL地址: " + nullPtr.address()); // 0
    }
    }
    }

    4.6 边界安全

    public class SegmentSafety {
    public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment seg = arena.allocate(8);

    // 正常访问
    seg.set(ValueLayout.JAVA_INT, 0, 42); // OK

    // 越界访问 → 抛出 IndexOutOfBoundsException
    try {
    seg.set(ValueLayout.JAVA_INT, 8, 100); // offset 8 + 4 > 8
    } catch (IndexOutOfBoundsException e) {
    System.out.println("捕获越界: " + e.getMessage());
    }

    // Arena关闭后访问 → 抛出 IllegalStateException
    }
    // 此处seg已失效
    // seg.get(ValueLayout.JAVA_INT, 0); // IllegalStateException!
    }
    }


    五、Arena(生命周期管理)

    5.1 Arena的作用

    Arena 管理一组 MemorySegment 的生命周期。当 Arena 关闭时,其分配的所有内存段都会被释放,对这些段的后续访问将抛出异常。

    5.2 Arena.ofConfined()(限定Arena)

    public class ConfinedArenaDemo {
    public static void main(String[] args) {
    // 只能由创建线程访问,性能最优
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment seg = arena.allocate(1024);
    seg.set(ValueLayout.JAVA_INT, 0, 42);
    System.out.println(seg.get(ValueLayout.JAVA_INT, 0)); // 42

    // 其他线程访问会抛出 WrongThreadException
    // new Thread(() -> seg.get(ValueLayout.JAVA_INT, 0)).start(); // 错误!
    }
    // 关闭后内存释放
    }
    }

    5.3 Arena.ofShared()(共享Arena)

    import java.util.concurrent.CountDownLatch;

    public class SharedArenaDemo {
    public static void main(String[] args) throws Exception {
    CountDownLatch latch = new CountDownLatch(2);

    try (Arena arena = Arena.ofShared()) {
    MemorySegment shared = arena.allocate(ValueLayout.JAVA_INT);
    shared.set(ValueLayout.JAVA_INT, 0, 0);

    // 多线程安全访问
    for (int i = 0; i < 2; i++) {
    new Thread(() -> {
    for (int j = 0; j < 1000; j++) {
    // 注意:非原子操作,仅演示可访问性
    int val = shared.get(ValueLayout.JAVA_INT, 0);
    shared.set(ValueLayout.JAVA_INT, 0, val + 1);
    }
    latch.countDown();
    }).start();
    }

    latch.await();
    System.out.println("结果: " + shared.get(ValueLayout.JAVA_INT, 0));
    }
    }
    }

    5.4 Arena.ofAuto()(自动Arena)

    public class AutoArenaDemo {
    // 由GC管理,无需手动关闭
    private static MemorySegment globalBuffer;

    public static void init() {
    Arena autoArena = Arena.ofAuto();
    globalBuffer = autoArena.allocate(4096);
    globalBuffer.set(ValueLayout.JAVA_INT, 0, 999);
    // 不需要关闭arena,GC会处理
    }

    public static int getValue() {
    // 只要globalBuffer可达,内存就不会被回收
    return globalBuffer.get(ValueLayout.JAVA_INT, 0);
    }

    public static void main(String[] args) {
    init();
    System.out.println(getValue()); // 999
    // 内存最终由GC回收(时机不确定)
    }
    }

    5.5 Arena.global()(全局Arena)

    public class GlobalArenaDemo {
    public static void main(String[] args) {
    // 用于包装已存在的全局内存(如C全局变量)
    // 永不释放
    Linker linker = Linker.nativeLinker();
    SymbolLookup lookup = linker.defaultLookup();

    // 例如访问C标准库的全局变量(如 stdout)
    // 实际使用中较少直接操作global arena
    Arena global = Arena.global();
    System.out.println("Global arena: " + global);
    }
    }

    5.6 Arena选择指南

    ┌────────────────────────────────────────────────────────────┐
    │ Arena 选择决策树 │
    ├────────────────────────────────────────────────────────────┤
    │ │
    │ 需要多线程访问? │
    │ ├── 否 → 需要确定性释放? │
    │ │ ├── 是 → Arena.ofConfined()(推荐) │
    │ │ └── 否 → Arena.ofAuto() │
    │ └── 是 → 需要确定性释放? │
    │ ├── 是 → Arena.ofShared() │
    │ └── 否 → Arena.ofAuto() │
    │ │
    │ 包装已存在的全局内存?→ Arena.global() │
    └────────────────────────────────────────────────────────────┘


    六、MemoryLayout(结构体布局)

    6.1 基本布局类型

    import java.lang.foreign.*;

    public class LayoutBasics {
    public static void main(String[] args) {
    // ValueLayout:基本类型
    ValueLayout intLayout = ValueLayout.JAVA_INT; // 4 bytes
    ValueLayout longLayout = ValueLayout.JAVA_LONG; // 8 bytes
    ValueLayout doubleLayout = ValueLayout.JAVA_DOUBLE; // 8 bytes
    ValueLayout addrLayout = ValueLayout.ADDRESS; // 平台相关(4或8)

    System.out.println("int大小: " + intLayout.byteSize());
    System.out.println("long大小: " + longLayout.byteSize());
    System.out.println("指针大小: " + addrLayout.byteSize());

    // 字节序
    ValueLayout bigEndianInt = ValueLayout.JAVA_INT.withOrder(
    java.nio.ByteOrder.BIG_ENDIAN);
    ValueLayout littleEndianInt = ValueLayout.JAVA_INT.withOrder(
    java.nio.ByteOrder.LITTLE_ENDIAN);
    }
    }

    6.2 结构体布局(StructLayout)

    public class StructLayoutDemo {
    // 对应C结构体:
    // struct Point {
    // int x; // offset 0, size 4
    // int y; // offset 4, size 4
    // double z; // offset 8, size 8(对齐到8字节)
    // };

    static final StructLayout POINT_LAYOUT = MemoryLayout.structLayout(
    ValueLayout.JAVA_INT.withName("x"),
    ValueLayout.JAVA_INT.withName("y"),
    ValueLayout.JAVA_DOUBLE.withName("z")
    );

    public static void main(String[] args) {
    System.out.println("Point大小: " + POINT_LAYOUT.byteSize()); // 16
    System.out.println("Point对齐: " + POINT_LAYOUT.byteAlignment()); // 8

    try (Arena arena = Arena.ofConfined()) {
    MemorySegment point = arena.allocate(POINT_LAYOUT);

    // 通过偏移量访问
    long xOffset = POINT_LAYOUT.byteOffset(
    MemoryLayout.PathElement.groupElement("x"));
    long yOffset = POINT_LAYOUT.byteOffset(
    MemoryLayout.PathElement.groupElement("y"));
    long zOffset = POINT_LAYOUT.byteOffset(
    MemoryLayout.PathElement.groupElement("z"));

    point.set(ValueLayout.JAVA_INT, xOffset, 10);
    point.set(ValueLayout.JAVA_INT, yOffset, 20);
    point.set(ValueLayout.JAVA_DOUBLE, zOffset, 3.5);

    System.out.printf("Point(%d, %d, %.1f)%n",
    point.get(ValueLayout.JAVA_INT, xOffset),
    point.get(ValueLayout.JAVA_INT, yOffset),
    point.get(ValueLayout.JAVA_DOUBLE, zOffset));
    }
    }
    }

    6.3 使用VarHandle高效访问

    import java.lang.invoke.VarHandle;

    public class VarHandleLayout {
    static final StructLayout RECT_LAYOUT = MemoryLayout.structLayout(
    ValueLayout.JAVA_INT.withName("x"),
    ValueLayout.JAVA_INT.withName("y"),
    ValueLayout.JAVA_INT.withName("width"),
    ValueLayout.JAVA_INT.withName("height")
    );

    // 创建VarHandle(一次创建,重复使用)
    static final VarHandle X_HANDLE = RECT_LAYOUT.varHandle(
    MemoryLayout.PathElement.groupElement("x"));
    static final VarHandle Y_HANDLE = RECT_LAYOUT.varHandle(
    MemoryLayout.PathElement.groupElement("y"));
    static final VarHandle WIDTH_HANDLE = RECT_LAYOUT.varHandle(
    MemoryLayout.PathElement.groupElement("width"));
    static final VarHandle HEIGHT_HANDLE = RECT_LAYOUT.varHandle(
    MemoryLayout.PathElement.groupElement("height"));

    public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment rect = arena.allocate(RECT_LAYOUT);

    // 使用VarHandle读写(比每次计算offset更高效)
    X_HANDLE.set(rect, 0L, 100);
    Y_HANDLE.set(rect, 0L, 200);
    WIDTH_HANDLE.set(rect, 0L, 800);
    HEIGHT_HANDLE.set(rect, 0L, 600);

    int x = (int) X_HANDLE.get(rect, 0L);
    int w = (int) WIDTH_HANDLE.get(rect, 0L);
    System.out.printf("Rect: x=%d, width=%d%n", x, w);
    }
    }
    }

    6.4 数组布局(SequenceLayout)

    public class SequenceLayoutDemo {
    // 对应C:int matrix[3][4];
    static final SequenceLayout MATRIX_LAYOUT = MemoryLayout.sequenceLayout(3,
    MemoryLayout.sequenceLayout(4, ValueLayout.JAVA_INT));

    public static void main(String[] args) {
    System.out.println("矩阵大小: " + MATRIX_LAYOUT.byteSize()); // 48 bytes

    // 二维数组的VarHandle
    VarHandle elementHandle = MATRIX_LAYOUT.varHandle(
    MemoryLayout.PathElement.sequenceElement(), // 行(动态索引)
    MemoryLayout.PathElement.sequenceElement() // 列(动态索引)
    );

    try (Arena arena = Arena.ofConfined()) {
    MemorySegment matrix = arena.allocate(MATRIX_LAYOUT);

    // 填充矩阵
    for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
    elementHandle.set(matrix, 0L, (long) i, (long) j, i * 4 + j);
    }
    }

    // 读取并打印
    for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
    int val = (int) elementHandle.get(matrix, 0L, (long) i, (long) j);
    System.out.printf("%3d", val);
    }
    System.out.println();
    }
    }
    }
    }

    6.5 嵌套结构体与联合体

    public class NestedLayoutDemo {
    // struct Address { char street[64]; int zip; };
    static final StructLayout ADDRESS_LAYOUT = MemoryLayout.structLayout(
    MemoryLayout.sequenceLayout(64, ValueLayout.JAVA_BYTE).withName("street"),
    ValueLayout.JAVA_INT.withName("zip")
    );

    // struct Person { char name[32]; int age; struct Address addr; };
    static final StructLayout PERSON_LAYOUT = MemoryLayout.structLayout(
    MemoryLayout.sequenceLayout(32, ValueLayout.JAVA_BYTE).withName("name"),
    ValueLayout.JAVA_INT.withName("age"),
    ADDRESS_LAYOUT.withName("address")
    );

    // union Variant { int i; double d; void* ptr; };
    static final UnionLayout VARIANT_LAYOUT = MemoryLayout.unionLayout(
    ValueLayout.JAVA_INT.withName("intValue"),
    ValueLayout.JAVA_DOUBLE.withName("doubleValue"),
    ValueLayout.ADDRESS.withName("ptrValue")
    );

    public static void main(String[] args) {
    System.out.println("Person大小: " + PERSON_LAYOUT.byteSize());
    System.out.println("Variant大小: " + VARIANT_LAYOUT.byteSize()); // 8(最大成员)

    // 访问嵌套成员
    VarHandle zipHandle = PERSON_LAYOUT.varHandle(
    MemoryLayout.PathElement.groupElement("address"),
    MemoryLayout.PathElement.groupElement("zip")
    );

    try (Arena arena = Arena.ofConfined()) {
    MemorySegment person = arena.allocate(PERSON_LAYOUT);
    zipHandle.set(person, 0L, 100000);
    int zip = (int) zipHandle.get(person, 0L);
    System.out.println("邮编: " + zip);
    }
    }
    }


    七、Linker(本地函数链接)

    7.1 获取Linker实例

    public class LinkerDemo {
    public static void main(String[] args) {
    // 获取平台原生链接器(单例)
    Linker linker = Linker.nativeLinker();

    // 获取默认符号查找(C标准库)
    SymbolLookup defaultLookup = linker.defaultLookup();

    // 查找函数地址
    MemorySegment printfAddr = defaultLookup.find("printf").orElseThrow();
    System.out.println("printf地址: 0x" + Long.toHexString(printfAddr.address()));

    MemorySegment mallocAddr = defaultLookup.find("malloc").orElseThrow();
    System.out.println("malloc地址: 0x" + Long.toHexString(mallocAddr.address()));
    }
    }

    7.2 下行调用(Downcall)

    public class DowncallDemo {
    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();
    SymbolLookup lookup = linker.defaultLookup();

    // 创建 abs 函数的 downcall handle
    // int abs(int n);
    MemorySegment absAddr = lookup.find("abs").orElseThrow();
    FunctionDescriptor absDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_INT, // 返回值
    ValueLayout.JAVA_INT // 参数
    );
    MethodHandle abs = linker.downcallHandle(absAddr, absDesc);

    // 调用
    int result = (int) abs.invokeExact(42);
    System.out.println("abs(-42) = " + result); // 42

    // 创建 pow 函数的 downcall handle
    // double pow(double base, double exp);
    MemorySegment powAddr = lookup.find("pow").orElseThrow();
    FunctionDescriptor powDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_DOUBLE,
    ValueLayout.JAVA_DOUBLE,
    ValueLayout.JAVA_DOUBLE
    );
    MethodHandle pow = linker.downcallHandle(powAddr, powDesc);

    double power = (double) pow.invokeExact(2.0, 10.0);
    System.out.println("pow(2, 10) = " + power); // 1024.0
    }
    }

    7.3 上行调用(Upcall)

    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodType;

    public class UpcallDemo {
    // Java回调方法(将被C代码调用)
    static int compareInts(MemorySegment a, MemorySegment b) {
    int va = a.get(ValueLayout.JAVA_INT, 0);
    int vb = b.get(ValueLayout.JAVA_INT, 0);
    return Integer.compare(va, vb);
    }

    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();

    // 创建upcall stub(将Java方法包装为C函数指针)
    MethodHandle compareHandle = MethodHandles.lookup().findStatic(
    UpcallDemo.class,
    "compareInts",
    MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class)
    );

    // 描述C回调签名:int (*)(const void*, const void*)
    FunctionDescriptor compareDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_INT,
    ValueLayout.ADDRESS,
    ValueLayout.ADDRESS
    );

    try (Arena arena = Arena.ofConfined()) {
    // 创建函数指针
    MemorySegment callbackPtr = linker.upcallStub(
    compareHandle, compareDesc, arena);

    System.out.println("回调指针: 0x" + Long.toHexString(callbackPtr.address()));
    // 可以将callbackPtr传给C函数(如qsort)
    }
    // Arena关闭后,callbackPtr失效
    }
    }

    7.4 加载自定义动态库

    import java.nio.file.Path;

    public class LibraryLoadDemo {
    public static void main(String[] args) throws Throwable {
    // 方式1:通过路径加载
    Path libPath = Path.of("/usr/lib/libm.so.6"); // Linux
    // Path libPath = Path.of("C:\\\\Windows\\\\System32\\\\msvcrt.dll"); // Windows

    try (Arena arena = Arena.ofConfined()) {
    SymbolLookup libLookup = SymbolLookup.libraryLookup(libPath, arena);

    // 查找库中的符号
    MemorySegment sinAddr = libLookup.find("sin").orElseThrow();
    System.out.println("sin地址: 0x" + Long.toHexString(sinAddr.address()));
    }

    // 方式2:通过库名加载(使用系统搜索路径)
    try (Arena arena = Arena.ofConfined()) {
    SymbolLookup libLookup = SymbolLookup.libraryLookup("m", arena);
    MemorySegment cosAddr = libLookup.find("cos").orElseThrow();

    Linker linker = Linker.nativeLinker();
    FunctionDescriptor cosDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE);
    MethodHandle cos = linker.downcallHandle(cosAddr, cosDesc);

    double result = (double) cos.invokeExact(0.0);
    System.out.println("cos(0) = " + result); // 1.0
    }
    }
    }


    八、FunctionDescriptor(函数签名描述)

    8.1 创建函数描述符

    public class DescriptorDemo {
    public static void main(String[] args) {
    // 有返回值的函数:int add(int, int)
    FunctionDescriptor addDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_INT, // 返回类型
    ValueLayout.JAVA_INT, // 参数1
    ValueLayout.JAVA_INT // 参数2
    );

    // void函数:void printf(const char*, …)
    FunctionDescriptor printfDesc = FunctionDescriptor.ofVoid(
    ValueLayout.ADDRESS // const char*
    // 可变参数不在descriptor中描述
    );

    // 指针参数:void* malloc(size_t)
    FunctionDescriptor mallocDesc = FunctionDescriptor.of(
    ValueLayout.ADDRESS, // 返回 void*
    ValueLayout.JAVA_LONG // size_t(64位平台)
    );

    // 无参函数:int rand(void)
    FunctionDescriptor randDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_INT // 仅返回类型
    );

    // 查询描述符信息
    System.out.println("返回类型: " + addDesc.returnLayout());
    System.out.println("参数列表: " + addDesc.argumentLayouts());
    }
    }

    8.2 C类型到Java布局映射

    public class TypeMapping {
    /*
    * C类型 Java ValueLayout 大小(64位Linux)
    * ─────────────────────────────────────────────────────────────
    * char JAVA_BYTE 1
    * short JAVA_SHORT 2
    * int JAVA_INT 4
    * long JAVA_LONG 8 (Linux) / 4 (Windows)
    * long long JAVA_LONG 8
    * float JAVA_FLOAT 4
    * double JAVA_DOUBLE 8
    * char* ADDRESS 8
    * void* ADDRESS 8
    * size_t JAVA_LONG 8 (64位)
    * int8_t JAVA_BYTE 1
    * int16_t JAVA_SHORT 2
    * int32_t JAVA_INT 4
    * int64_t JAVA_LONG 8
    * uint8_t JAVA_BYTE (unsigned) 1
    * uint16_t JAVA_SHORT (unsigned) 2
    * uint32_t JAVA_INT (unsigned) 4
    * uint64_t JAVA_LONG (unsigned) 8
    */

    // 注意:C的long在Windows 64位上是4字节!
    // 跨平台时需要特别处理
    static ValueLayout cLong() {
    String os = System.getProperty("os.name").toLowerCase();
    if (os.contains("win")) {
    return ValueLayout.JAVA_INT; // Windows: long = 4 bytes
    }
    return ValueLayout.JAVA_LONG; // Linux/Mac: long = 8 bytes
    }
    }

    8.3 结构体作为参数/返回值

    public class StructDescriptor {
    // struct Point { int x; int y; };
    // struct Point make_point(int x, int y);
    // double distance(struct Point p1, struct Point p2);

    static final StructLayout POINT = MemoryLayout.structLayout(
    ValueLayout.JAVA_INT.withName("x"),
    ValueLayout.JAVA_INT.withName("y")
    );

    public static void main(String[] args) {
    // 返回结构体的函数
    FunctionDescriptor makePointDesc = FunctionDescriptor.of(
    POINT, // 返回 struct Point
    ValueLayout.JAVA_INT, // int x
    ValueLayout.JAVA_INT // int y
    );

    // 结构体按值传递(注意:大结构体可能按引用传递,取决于ABI)
    FunctionDescriptor distanceDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_DOUBLE, // 返回 double
    POINT, // struct Point p1(按值)
    POINT // struct Point p2(按值)
    );

    // 结构体指针传递(更常见)
    // double distance_ptr(const struct Point* p1, const struct Point* p2);
    FunctionDescriptor distancePtrDesc = FunctionDescriptor.of(
    ValueLayout.JAVA_DOUBLE,
    ValueLayout.ADDRESS, // const struct Point*
    ValueLayout.ADDRESS // const struct Point*
    );
    }
    }


    九、MethodHandle调用本地函数

    9.1 invoke与invokeExact

    public class MethodHandleInvoke {
    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();
    SymbolLookup lookup = linker.defaultLookup();

    MemorySegment absAddr = lookup.find("abs").orElseThrow();
    FunctionDescriptor desc = FunctionDescriptor.of(
    ValueLayout.JAVA_INT, ValueLayout.JAVA_INT);
    MethodHandle abs = linker.downcallHandle(absAddr, desc);

    // invokeExact:严格类型匹配,不做自动转换
    int r1 = (int) abs.invokeExact(5); // 必须精确匹配int

    // invoke:允许自动类型转换(装箱/拆箱/拓宽)
    Object r2 = abs.invoke(5); // 返回Object,内部做转换
    System.out.println("invokeExact: " + r1);
    System.out.println("invoke: " + r2);

    // 性能提示:invokeExact更快(无适配开销)
    // 推荐在性能敏感路径使用invokeExact
    }
    }

    9.2 缓存MethodHandle

    public class CachedHandles {
    private static final Linker LINKER = Linker.nativeLinker();
    private static final SymbolLookup LOOKUP = LINKER.defaultLookup();

    // 缓存MethodHandle(创建成本高,应复用)
    private static final MethodHandle STRLEN;
    private static final MethodHandle PUTS;
    private static final MethodHandle MALLOC;
    private static final MethodHandle FREE;

    static {
    try {
    STRLEN = LINKER.downcallHandle(
    LOOKUP.find("strlen").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
    );

    PUTS = LINKER.downcallHandle(
    LOOKUP.find("puts").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)
    );

    MALLOC = LINKER.downcallHandle(
    LOOKUP.find("malloc").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
    );

    FREE = LINKER.downcallHandle(
    LOOKUP.find("free").orElseThrow(),
    FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)
    );
    } catch (Exception e) {
    throw new ExceptionInInitializerError(e);
    }
    }

    public static long strlen(String s) throws Throwable {
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment cStr = arena.allocateFrom(s);
    return (long) STRLEN.invokeExact(cStr);
    }
    }

    public static void main(String[] args) throws Throwable {
    System.out.println(strlen("Hello")); // 5
    PUTS.invokeExact(
    Arena.ofAuto().allocateFrom("Hello from C puts!"));
    }
    }

    9.3 可变参数函数调用

    public class VariadicCall {
    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();
    SymbolLookup lookup = linker.defaultLookup();

    // printf 是可变参数函数
    // 对于可变参数,需要为每次调用指定具体签名
    MemorySegment printfAddr = lookup.find("printf").orElseThrow();

    // 调用 printf("Hello %s, you are %d\\n", name, age)
    // 需要描述实际参数类型
    FunctionDescriptor printfDesc = FunctionDescriptor.ofVoid(
    ValueLayout.ADDRESS, // format string
    ValueLayout.ADDRESS, // %s → char*
    ValueLayout.JAVA_INT // %d → int
    );

    // 使用 Linker.Option.firstVariadicArg(1) 标记可变参数起始位置
    MethodHandle printf = linker.downcallHandle(
    printfAddr,
    printfDesc,
    Linker.Option.firstVariadicArg(1) // 第1个参数开始是可变参数
    );

    try (Arena arena = Arena.ofConfined()) {
    MemorySegment fmt = arena.allocateFrom("Hello %s, age %d!%n");
    MemorySegment name = arena.allocateFrom("Panama");
    printf.invokeExact(fmt, name, 22);
    // 输出: Hello Panama, age 22!
    }
    }
    }


    十、调用C标准库实战

    10.1 strlen – 字符串长度

    public class StrlenExample {
    private static final Linker LINKER = Linker.nativeLinker();
    private static final MethodHandle STRLEN;

    static {
    try {
    STRLEN = LINKER.downcallHandle(
    LINKER.defaultLookup().find("strlen").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
    );
    } catch (Exception e) {
    throw new ExceptionInInitializerError(e);
    }
    }

    public static long strlen(String s) throws Throwable {
    try (Arena arena = Arena.ofConfined()) {
    return (long) STRLEN.invokeExact(arena.allocateFrom(s));
    }
    }

    public static void main(String[] args) throws Throwable {
    System.out.println(strlen("Hello, World!")); // 13
    System.out.println(strlen("Project Panama")); // 14
    System.out.println(strlen("")); // 0
    System.out.println(strlen("中文测试")); // 12 (UTF-8)
    }
    }

    10.2 qsort – 快速排序

    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodType;

    public class QsortExample {
    private static final Linker LINKER = Linker.nativeLinker();
    private static final SymbolLookup LOOKUP = LINKER.defaultLookup();

    // 比较器:int compare(const void* a, const void* b)
    static int intCompare(MemorySegment a, MemorySegment b) {
    int va = a.get(ValueLayout.JAVA_INT, 0);
    int vb = b.get(ValueLayout.JAVA_INT, 0);
    return Integer.compare(va, vb);
    }

    public static void main(String[] args) throws Throwable {
    // void qsort(void* base, size_t nmemb, size_t size,
    // int (*compar)(const void*, const void*));
    MethodHandle qsort = LINKER.downcallHandle(
    LOOKUP.find("qsort").orElseThrow(),
    FunctionDescriptor.ofVoid(
    ValueLayout.ADDRESS, // base
    ValueLayout.JAVA_LONG, // nmemb
    ValueLayout.JAVA_LONG, // size
    ValueLayout.ADDRESS // compar
    )
    );

    // 创建比较器回调
    MethodHandle compareHandle = MethodHandles.lookup().findStatic(
    QsortExample.class, "intCompare",
    MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class)
    );

    try (Arena arena = Arena.ofConfined()) {
    // 准备数据
    int[] data = {64, 25, 12, 22, 11, 90, 1, 99, 45};
    MemorySegment array = arena.allocateArray(ValueLayout.JAVA_INT, data);

    // 创建upcall stub
    MemorySegment comparator = LINKER.upcallStub(
    compareHandle,
    FunctionDescriptor.of(ValueLayout.JAVA_INT,
    ValueLayout.ADDRESS, ValueLayout.ADDRESS),
    arena
    );

    // 调用qsort
    qsort.invokeExact(
    array,
    (long) data.length,
    (long) ValueLayout.JAVA_INT.byteSize(),
    comparator
    );

    // 读取排序结果
    int[] sorted = array.toArray(ValueLayout.JAVA_INT);
    System.out.println("排序后: " + java.util.Arrays.toString(sorted));
    // [1, 11, 12, 22, 25, 45, 64, 99, 20]
    }
    }
    }

    10.3 memcpy与memset

    public class MemOpsExample {
    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();
    SymbolLookup lookup = linker.defaultLookup();

    // void* memcpy(void* dest, const void* src, size_t n);
    MethodHandle memcpy = linker.downcallHandle(
    lookup.find("memcpy").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.ADDRESS,
    ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
    );

    // void* memset(void* s, int c, size_t n);
    MethodHandle memset = linker.downcallHandle(
    lookup.find("memset").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.ADDRESS,
    ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG)
    );

    try (Arena arena = Arena.ofConfined()) {
    MemorySegment src = arena.allocateFrom("Hello, Panama!");
    MemorySegment dest = arena.allocate(64);

    // memset清零
    memset.invokeExact(dest, 0, 64L);

    // memcpy复制
    memcpy.invokeExact(dest, src, src.byteSize());

    System.out.println("复制结果: " + dest.getString(0));
    // Hello, Panama!
    }
    }
    }

    10.4 调用数学库

    public class MathLibExample {
    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();
    SymbolLookup lookup = linker.defaultLookup();

    // double sqrt(double x)
    MethodHandle sqrt = linker.downcallHandle(
    lookup.find("sqrt").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
    );

    // double sin(double x)
    MethodHandle sin = linker.downcallHandle(
    lookup.find("sin").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
    );

    // double atan2(double y, double x)
    MethodHandle atan2 = linker.downcallHandle(
    lookup.find("atan2").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE,
    ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
    );

    double sqrtResult = (double) sqrt.invokeExact(144.0);
    double sinResult = (double) sin.invokeExact(Math.PI / 2);
    double atanResult = (double) atan2.invokeExact(1.0, 1.0);

    System.out.printf("sqrt(144) = %.1f%n", sqrtResult); // 12.0
    System.out.printf("sin(PI/2) = %.1f%n", sinResult); // 1.0
    System.out.printf("atan2(1,1) = %.6f%n", atanResult); // 0.785398
    }
    }


    十一、调用自定义本地库

    11.1 编写C源文件

    // mylib.h
    #ifndef MYLIB_H
    #define MYLIB_H

    typedef struct {
    double real;
    double imag;
    } Complex;

    Complex complex_add(Complex a, Complex b);
    Complex complex_multiply(Complex a, Complex b);
    double complex_magnitude(Complex c);

    // 数组操作
    void array_scale(double* arr, int len, double factor);
    double array_dot(const double* a, const double* b, int len);

    // 回调
    typedef void (*callback_t)(int progress, void* user_data);
    void long_running_task(int iterations, callback_t cb, void* user_data);

    #endif

    // mylib.c
    #include "mylib.h"
    #include <math.h>

    Complex complex_add(Complex a, Complex b) {
    Complex result = {a.real + b.real, a.imag + b.imag};
    return result;
    }

    Complex complex_multiply(Complex a, Complex b) {
    Complex result = {
    a.real * b.real a.imag * b.imag,
    a.real * b.imag + a.imag * b.real
    };
    return result;
    }

    double complex_magnitude(Complex c) {
    return sqrt(c.real * c.real + c.imag * c.imag);
    }

    void array_scale(double* arr, int len, double factor) {
    for (int i = 0; i < len; i++) {
    arr[i] *= factor;
    }
    }

    double array_dot(const double* a, const double* b, int len) {
    double sum = 0.0;
    for (int i = 0; i < len; i++) {
    sum += a[i] * b[i];
    }
    return sum;
    }

    void long_running_task(int iterations, callback_t cb, void* user_data) {
    for (int i = 0; i < iterations; i++) {
    // 模拟工作…
    if (cb && (i % (iterations / 10) == 0)) {
    cb(i * 100 / iterations, user_data);
    }
    }
    }

    11.2 编译动态库

    # Linux
    gcc -shared -fPIC -o libmylib.so mylib.c -lm

    # macOS
    gcc -shared -fPIC -o libmylib.dylib mylib.c -lm

    # Windows (MSVC)
    cl /LD mylib.c /Fe:mylib.dll

    # Windows (MinGW)
    gcc -shared -o mylib.dll mylib.c -lm

    11.3 Java端调用

    import java.nio.file.Path;

    public class CustomLibDemo {
    // Complex结构体布局
    static final StructLayout COMPLEX_LAYOUT = MemoryLayout.structLayout(
    ValueLayout.JAVA_DOUBLE.withName("real"),
    ValueLayout.JAVA_DOUBLE.withName("imag")
    );

    static final VarHandle REAL_HANDLE = COMPLEX_LAYOUT.varHandle(
    MemoryLayout.PathElement.groupElement("real"));
    static final VarHandle IMAG_HANDLE = COMPLEX_LAYOUT.varHandle(
    MemoryLayout.PathElement.groupElement("imag"));

    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();

    // 加载自定义库
    Path libPath = Path.of("./libmylib.so"); // 根据平台调整
    try (Arena arena = Arena.ofConfined()) {
    SymbolLookup lib = SymbolLookup.libraryLookup(libPath, arena);

    // complex_add
    MethodHandle complexAdd = linker.downcallHandle(
    lib.find("complex_add").orElseThrow(),
    FunctionDescriptor.of(COMPLEX_LAYOUT, COMPLEX_LAYOUT, COMPLEX_LAYOUT)
    );

    // 准备参数
    MemorySegment a = arena.allocate(COMPLEX_LAYOUT);
    REAL_HANDLE.set(a, 0L, 3.0);
    IMAG_HANDLE.set(a, 0L, 4.0);

    MemorySegment b = arena.allocate(COMPLEX_LAYOUT);
    REAL_HANDLE.set(b, 0L, 1.0);
    IMAG_HANDLE.set(b, 0L, 2.0);

    // 调用
    MemorySegment result = (MemorySegment) complexAdd.invokeExact(a, b);
    double real = (double) REAL_HANDLE.get(result, 0L);
    double imag = (double) IMAG_HANDLE.get(result, 0L);
    System.out.printf("(3+4i) + (1+2i) = %.1f+%.1fi%n", real, imag);
    // (3+4i) + (1+2i) = 4.0+6.0i

    // array_dot
    MethodHandle arrayDot = linker.downcallHandle(
    lib.find("array_dot").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE,
    ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)
    );

    MemorySegment vecA = arena.allocateArray(ValueLayout.JAVA_DOUBLE,
    new double[]{1.0, 2.0, 3.0});
    MemorySegment vecB = arena.allocateArray(ValueLayout.JAVA_DOUBLE,
    new double[]{4.0, 5.0, 6.0});

    double dot = (double) arrayDot.invokeExact(vecA, vecB, 3);
    System.out.println("点积: " + dot); // 32.0
    }
    }
    }


    十二、回调(Upcall Stub)

    12.1 基本概念

    Upcall Stub 将一个 Java MethodHandle 包装为一个本地函数指针(MemorySegment),使得C代码可以回调Java方法。

    ┌──────────────┐ downcall ┌──────────────┐
    │ Java Code │ ────────────────→ │ C Function │
    │ │ ←──────────────── │ │
    └──────────────┘ upcall └──────────────┘
    (callback)

    12.2 进度回调示例

    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodType;

    public class CallbackDemo {
    // 回调方法:void on_progress(int progress, void* user_data)
    static void onProgress(int progress, MemorySegment userData) {
    System.out.println("进度: " + progress + "%");
    }

    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();

    try (Arena arena = Arena.ofConfined()) {
    SymbolLookup lib = SymbolLookup.libraryLookup(
    Path.of("./libmylib.so"), arena);

    // void long_running_task(int iterations, callback_t cb, void* user_data)
    MethodHandle task = linker.downcallHandle(
    lib.find("long_running_task").orElseThrow(),
    FunctionDescriptor.ofVoid(
    ValueLayout.JAVA_INT, // iterations
    ValueLayout.ADDRESS, // callback
    ValueLayout.ADDRESS // user_data
    )
    );

    // 创建回调stub
    MethodHandle progressHandle = MethodHandles.lookup().findStatic(
    CallbackDemo.class, "onProgress",
    MethodType.methodType(void.class, int.class, MemorySegment.class)
    );

    MemorySegment callbackStub = linker.upcallStub(
    progressHandle,
    FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT, ValueLayout.ADDRESS),
    arena // 生命周期绑定
    );

    // 调用(C代码会回调Java)
    task.invokeExact(1000000, callbackStub, MemorySegment.NULL);
    }
    }
    }

    12.3 排序比较器回调

    public class SortCallback {
    // 降序比较器
    static int descendingCompare(MemorySegment a, MemorySegment b) {
    double va = a.get(ValueLayout.JAVA_DOUBLE, 0);
    double vb = b.get(ValueLayout.JAVA_DOUBLE, 0);
    return Double.compare(vb, va); // 降序
    }

    public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();
    SymbolLookup lookup = linker.defaultLookup();

    MethodHandle qsort = linker.downcallHandle(
    lookup.find("qsort").orElseThrow(),
    FunctionDescriptor.ofVoid(
    ValueLayout.ADDRESS, ValueLayout.JAVA_LONG,
    ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
    );

    MethodHandle compHandle = MethodHandles.lookup().findStatic(
    SortCallback.class, "descendingCompare",
    MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class)
    );

    try (Arena arena = Arena.ofConfined()) {
    double[] data = {3.14, 1.41, 2.72, 0.57, 1.73};
    MemorySegment arr = arena.allocateArray(ValueLayout.JAVA_DOUBLE, data);

    MemorySegment comp = linker.upcallStub(
    compHandle,
    FunctionDescriptor.of(ValueLayout.JAVA_INT,
    ValueLayout.ADDRESS, ValueLayout.ADDRESS),
    arena
    );

    qsort.invokeExact(arr, (long) data.length,
    ValueLayout.JAVA_DOUBLE.byteSize(), comp);

    double[] sorted = arr.toArray(ValueLayout.JAVA_DOUBLE);
    System.out.println("降序: " + java.util.Arrays.toString(sorted));
    // [3.14, 2.72, 1.73, 1.41, 0.57]
    }
    }
    }

    12.4 Upcall注意事项

    public class UpcallNotes {
    /*
    * 重要注意事项:
    *
    * 1. 生命周期:Upcall stub绑定到Arena,Arena关闭后stub失效
    * – 不要将stub存储在比Arena更长生命周期的地方
    *
    * 2. 线程安全:
    * – C代码可能从任意线程调用upcall
    * – 回调方法必须是线程安全的
    * – 使用Arena.ofShared()如果回调中需要访问共享内存
    *
    * 3. 异常处理:
    * – 回调方法中不要抛出受检异常
    * – 未捕获异常会导致未定义行为
    * – 建议在回调中try-catch所有异常
    *
    * 4. 性能:
    * – Upcall比downcall开销更大
    * – 避免在紧密循环中频繁upcall
    * – 考虑批量处理减少回调次数
    *
    * 5. 重入:
    * – C代码可能在upcall中再次downcall到Java
    * – 注意避免死锁
    */

    // 安全的回调实现示例
    static void safeCallback(int code, MemorySegment data) {
    try {
    // 业务逻辑
    System.out.println("Callback code: " + code);
    } catch (Exception e) {
    // 绝不让异常逃逸到C代码
    e.printStackTrace();
    }
    }
    }


    十三、与JNI性能对比

    13.1 基准测试代码

    public class PanamaVsJniBenchmark {
    // 测试1:简单函数调用开销
    // JNI: native int nativeAdd(int a, int b);
    // Panama: int add(int a, int b) via downcall

    private static final Linker LINKER = Linker.nativeLinker();
    private static final MethodHandle C_ABS;

    static {
    try {
    C_ABS = LINKER.downcallHandle(
    LINKER.defaultLookup().find("abs").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)
    );
    } catch (Exception e) {
    throw new ExceptionInInitializerError(e);
    }
    }

    // Panama方式
    public static int panamaAbs(int x) throws Throwable {
    return (int) C_ABS.invokeExact(x);
    }

    // JNI方式(对比)
    // public static native int jniAbs(int x);

    public static void main(String[] args) throws Throwable {
    int iterations = 10_000_000;

    // 预热
    for (int i = 0; i < 100_000; i++) {
    panamaAbs(i);
    }

    // Panama基准
    long start = System.nanoTime();
    int sum = 0;
    for (int i = 0; i < iterations; i++) {
    sum += panamaAbs(i);
    }
    long panamaTime = System.nanoTime() start;

    System.out.printf("Panama: %d ms (%.1f ns/call)%n",
    panamaTime / 1_000_000,
    (double) panamaTime / iterations);
    // 典型结果:~5-10 ns/call

    // JNI典型结果:~50-100 ns/call
    // 纯Java Math.abs:~0.3 ns/call(JIT内联后)
    }
    }

    13.2 内存操作性能对比

    public class MemoryPerfCompare {
    public static void main(String[] args) {
    int size = 1_000_000; // 100万个int

    // Panama:直接操作堆外内存(零拷贝)
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment segment = arena.allocateArray(ValueLayout.JAVA_INT, size);

    long start = System.nanoTime();
    for (int i = 0; i < size; i++) {
    segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
    }
    long writeTime = System.nanoTime() start;

    start = System.nanoTime();
    int sum = 0;
    for (int i = 0; i < size; i++) {
    sum += segment.getAtIndex(ValueLayout.JAVA_INT, i);
    }
    long readTime = System.nanoTime() start;

    System.out.printf("Panama写入: %.2f ms%n", writeTime / 1e6);
    System.out.printf("Panama读取: %.2f ms%n", readTime / 1e6);
    }

    // JNI方式(对比说明):
    // 1. GetIntArrayElements → 可能复制整个数组 (~数ms)
    // 2. 操作本地副本
    // 3. ReleaseIntArrayElements → 可能再次复制 (~数ms)
    // 总开销远大于Panama的直接访问
    }
    }

    13.3 性能对比总结

    ┌─────────────────────────────────────────────────────────────┐
    │ Panama vs JNI 性能对比 │
    ├─────────────────────┬──────────────┬────────────────────────┤
    │ 操作 │ JNI │ Panama │
    ├─────────────────────┼──────────────┼────────────────────────┤
    │ 简单函数调用 │ ~50-100 ns │ ~5-10 ns │
    │ 带字符串参数调用 │ ~200-500 ns │ ~50-100 ns │
    │ 大数组传输(1MB) │ ~1-5 ms │ ~0 (零拷贝) │
    │ 内存分配/释放 │ ~100 ns │ ~10 ns (Arena批量) │
    │ 回调调用 │ ~100-200 ns │ ~20-50 ns │
    ├─────────────────────┼──────────────┼────────────────────────┤
    │ GC压力 │ 高(临时对象)│ 低(Arena管理) │
    │ JIT优化 │ 阻止内联 │ 可优化 │
    │ 内存泄漏风险 │ 高 │ 极低 │
    └─────────────────────┴──────────────┴────────────────────────┘


    十四、jextract工具

    14.1 概述

    jextract 是一个独立工具,能从C头文件自动生成Java绑定代码,大幅减少手工编写绑定的工作量。

    14.2 安装与使用

    # 下载 jextract(从 https://jdk.java.net/jextract/)
    # 解压后使用

    # 基本用法:从C头文件生成Java代码
    jextract –source \\
    -t com.example.native \\
    -l m \\
    –header-file-name math.h \\
    /usr/include/math.h

    # 参数说明:
    # –source → 生成Java源码(而非class文件)
    # -t <package> → 目标Java包名
    # -l <lib> → 链接的库名
    # –header-file-name → 头文件名(用于生成类名)
    # 最后一个参数 → 头文件路径

    14.3 生成代码示例

    # 假设有头文件 mylib.h
    jextract –source -t com.example.mylib -l mylib mylib.h

    // 自动生成的代码(简化示意):com/example/mylib/mylib_h.java
    package com.example.mylib;

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

    public class mylib_h {
    // 库加载
    static final SymbolLookup LIBRARY = SymbolLookup.libraryLookup("mylib", Arena.ofAuto());
    static final Linker LINKER = Linker.nativeLinker();

    // struct Complex 布局
    public static final StructLayout Complex$LAYOUT = MemoryLayout.structLayout(
    ValueLayout.JAVA_DOUBLE.withName("real"),
    ValueLayout.JAVA_DOUBLE.withName("imag")
    );

    public static final VarHandle Complex$real$VH =
    Complex$LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("real"));
    public static final VarHandle Complex$imag$VH =
    Complex$LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("imag"));

    // complex_add 函数
    static final MethodHandle complex_add$MH = LINKER.downcallHandle(
    LIBRARY.find("complex_add").orElseThrow(),
    FunctionDescriptor.of(Complex$LAYOUT, Complex$LAYOUT, Complex$LAYOUT)
    );

    public static MemorySegment complex_add(MemorySegment a, MemorySegment b) {
    try {
    return (MemorySegment) complex_add$MH.invokeExact(a, b);
    } catch (Throwable e) {
    throw new AssertionError(e);
    }
    }

    // array_dot 函数
    static final MethodHandle array_dot$MH = LINKER.downcallHandle(
    LIBRARY.find("array_dot").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE,
    ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)
    );

    public static double array_dot(MemorySegment a, MemorySegment b, int len) {
    try {
    return (double) array_dot$MH.invokeExact(a, b, len);
    } catch (Throwable e) {
    throw new AssertionError(e);
    }
    }
    }

    14.4 使用生成的代码

    import com.example.mylib.mylib_h;

    public class UseGenerated {
    public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
    // 使用生成的布局
    MemorySegment a = arena.allocate(mylib_h.Complex$LAYOUT);
    mylib_h.Complex$real$VH.set(a, 0L, 3.0);
    mylib_h.Complex$imag$VH.set(a, 0L, 4.0);

    MemorySegment b = arena.allocate(mylib_h.Complex$LAYOUT);
    mylib_h.Complex$real$VH.set(b, 0L, 1.0);
    mylib_h.Complex$imag$VH.set(b, 0L, 2.0);

    // 调用生成的函数包装
    MemorySegment result = mylib_h.complex_add(a, b);
    double real = (double) mylib_h.Complex$real$VH.get(result, 0L);
    double imag = (double) mylib_h.Complex$imag$VH.get(result, 0L);
    System.out.printf("结果: %.1f + %.1fi%n", real, imag);
    }
    }
    }

    14.5 jextract配置选项

    # 过滤:只生成特定函数/结构体
    jextract –source \\
    -t com.example.lib \\
    –include-function strlen \\
    –include-function qsort \\
    –include-struct timeval \\
    –include-macro NULL \\
    -I /usr/include \\
    /usr/include/string.h

    # 处理系统头文件依赖
    jextract –source \\
    -t com.example.lib \\
    -I /usr/include \\
    -I /usr/include/x86_64-linux-gnu \\
    -D __STDC_VERSION__=201112L \\
    mylib.h


    十五、最佳实践

    15.1 封装为Java友好API

    /**
    * 将Panama底层调用封装为面向对象的Java API
    */

    public class NativeMath implements AutoCloseable {
    private final Arena arena;
    private final SymbolLookup lib;
    private final Linker linker;

    // 缓存所有MethodHandle
    private final MethodHandle sqrtHandle;
    private final MethodHandle powHandle;

    public NativeMath(String libPath) {
    this.arena = Arena.ofShared();
    this.lib = SymbolLookup.libraryLookup(Path.of(libPath), arena);
    this.linker = Linker.nativeLinker();

    try {
    this.sqrtHandle = linker.downcallHandle(
    lib.find("sqrt").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
    );
    this.powHandle = linker.downcallHandle(
    lib.find("pow").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE,
    ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
    );
    } catch (Exception e) {
    arena.close();
    throw new RuntimeException("Failed to load native library", e);
    }
    }

    public double sqrt(double x) {
    try {
    return (double) sqrtHandle.invokeExact(x);
    } catch (Throwable e) {
    throw new RuntimeException("Native call failed", e);
    }
    }

    public double pow(double base, double exp) {
    try {
    return (double) powHandle.invokeExact(base, exp);
    } catch (Throwable e) {
    throw new RuntimeException("Native call failed", e);
    }
    }

    @Override
    public void close() {
    arena.close();
    }

    // 使用示例
    public static void main(String[] args) {
    try (NativeMath math = new NativeMath("./libm.so")) {
    System.out.println(math.sqrt(2.0)); // 1.414…
    System.out.println(math.pow(2, 10)); // 1024.0
    }
    }
    }

    15.2 内存管理最佳实践

    public class MemoryBestPractices {

    // 实践1:始终使用try-with-resources
    public void goodPractice() {
    try (Arena arena = Arena.ofConfined()) {
    MemorySegment seg = arena.allocate(1024);
    // 使用seg…
    } // 自动释放,即使发生异常
    }

    // 实践2:批量分配,避免循环中频繁创建Arena
    public void batchAllocation() {
    try (Arena arena = Arena.ofConfined()) {
    // 一次性分配所有需要的内存
    MemorySegment buffer1 = arena.allocate(4096);
    MemorySegment buffer2 = arena.allocate(4096);
    MemorySegment buffer3 = arena.allocate(4096);

    // 在循环中复用
    for (int i = 0; i < 1000; i++) {
    processWith(buffer1, buffer2, buffer3);
    }
    }
    }

    // 实践3:长生命周期数据用ofAuto
    private static MemorySegment configData;

    public static void loadConfig(byte[] rawData) {
    Arena auto = Arena.ofAuto();
    configData = auto.allocate(rawData.length);
    MemorySegment.copy(rawData, 0, configData,
    ValueLayout.JAVA_BYTE, 0, rawData.length);
    // 无需关闭,GC管理
    }

    // 实践4:避免不必要的内存拷贝
    public void zeroCopy(byte[] javaArray) {
    // 直接包装Java数组(零拷贝)
    MemorySegment view = MemorySegment.ofArray(javaArray);
    // 修改view直接影响javaArray
    view.set(ValueLayout.JAVA_BYTE, 0, (byte) 42);
    System.out.println(javaArray[0]); // 42
    }

    private void processWith(MemorySegment a, MemorySegment b, MemorySegment c) {
    // 处理逻辑
    }
    }

    15.3 错误处理

    public class ErrorHandling {
    // 检查本地函数返回值
    public static MemorySegment safeMalloc(long size) throws Throwable {
    Linker linker = Linker.nativeLinker();
    MethodHandle malloc = linker.downcallHandle(
    linker.defaultLookup().find("malloc").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
    );

    MemorySegment ptr = (MemorySegment) malloc.invokeExact(size);
    if (ptr.equals(MemorySegment.NULL)) {
    throw new OutOfMemoryError("Native malloc failed for size: " + size);
    }
    return ptr;
    }

    // 检查errno(通过__errno_location或类似函数)
    public static int getErrno() throws Throwable {
    Linker linker = Linker.nativeLinker();
    // Linux: int* __errno_location(void)
    MethodHandle errnoLoc = linker.downcallHandle(
    linker.defaultLookup().find("__errno_location").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.ADDRESS)
    );

    MemorySegment errnoPtr = (MemorySegment) errnoLoc.invokeExact();
    return errnoPtr.reinterpret(4).get(ValueLayout.JAVA_INT, 0);
    }

    // 安全的字符串操作
    public static String safeGetString(MemorySegment ptr, long maxLen) {
    if (ptr.equals(MemorySegment.NULL)) {
    return null;
    }
    MemorySegment bounded = ptr.reinterpret(maxLen);
    return bounded.getString(0);
    }
    }

    15.4 跨平台处理

    public class CrossPlatform {
    // 动态库文件扩展名
    public static String libExtension() {
    String os = System.getProperty("os.name").toLowerCase();
    if (os.contains("win")) return ".dll";
    if (os.contains("mac")) return ".dylib";
    return ".so";
    }

    // 库文件路径
    public static Path findLibrary(String baseName) {
    String os = System.getProperty("os.name").toLowerCase();
    String arch = System.getProperty("os.arch");

    String libName;
    if (os.contains("win")) {
    libName = baseName + ".dll";
    } else if (os.contains("mac")) {
    libName = "lib" + baseName + ".dylib";
    } else {
    libName = "lib" + baseName + ".so";
    }

    // 从资源目录或系统路径查找
    Path localPath = Path.of("native", os.contains("win") ? "windows" :
    os.contains("mac") ? "macos" : "linux", arch, libName);

    if (java.nio.file.Files.exists(localPath)) {
    return localPath.toAbsolutePath();
    }

    // 回退到系统搜索路径
    return Path.of(libName);
    }

    // C long的平台差异处理
    public static ValueLayout cLongLayout() {
    // Windows 64位:long = 4 bytes (LLP64模型)
    // Linux/Mac 64位:long = 8 bytes (LP64模型)
    boolean isWindows = System.getProperty("os.name")
    .toLowerCase().contains("win");
    return isWindows ? ValueLayout.JAVA_INT : ValueLayout.JAVA_LONG;
    }
    }

    15.5 性能优化清单

    public class PerformanceTips {
    /*
    * 性能优化要点:
    *
    * 1. 缓存MethodHandle
    * – 创建downcall handle成本高(~微秒级)
    * – 用static final缓存,避免每次调用重建
    *
    * 2. 使用invokeExact而非invoke
    * – invokeExact避免自动类型适配
    * – 性能差距在热路径上显著
    *
    * 3. 使用VarHandle访问结构体
    * – 避免每次通过byteOffset计算偏移
    * – VarHandle可被JIT优化
    *
    * 4. 批量操作
    * – 一次传递大数组,而非循环调用小数据
    * – 减少Java↔Native边界跨越次数
    *
    * 5. Arena复用
    * – 循环外创建Arena,循环内复用
    * – 避免每次迭代都allocate/close
    *
    * 6. 避免不必要的reinterpret
    * – 每次reinterpret创建新对象
    * – 预先知道大小时直接分配正确大小
    *
    * 7. 选择合适的Arena类型
    * – 单线程场景用ofConfined(无同步开销)
    * – 避免在不需要共享时使用ofShared
    */

    // 反面示例:每次调用都创建handle
    public static int badAbs(int x) throws Throwable {
    Linker linker = Linker.nativeLinker();
    MethodHandle abs = linker.downcallHandle( // 每次创建!慢!
    linker.defaultLookup().find("abs").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)
    );
    return (int) abs.invoke(x); // invoke而非invokeExact!慢!
    }

    // 正面示例:缓存handle + invokeExact
    private static final MethodHandle GOOD_ABS;
    static {
    try {
    Linker linker = Linker.nativeLinker();
    GOOD_ABS = linker.downcallHandle(
    linker.defaultLookup().find("abs").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)
    );
    } catch (Exception e) {
    throw new ExceptionInInitializerError(e);
    }
    }

    public static int goodAbs(int x) throws Throwable {
    return (int) GOOD_ABS.invokeExact(x); // 快速!
    }
    }

    15.6 完整工程结构示例

    my-native-project/
    ├── src/main/java/
    │ └── com/example/
    │ ├── NativeLib.java ← 封装层(对外API)
    │ ├── NativeLibLoader.java ← 库加载逻辑
    │ └── internal/
    │ ├── bindings.java ← jextract生成的绑定
    │ └── Layouts.java ← 自定义布局常量
    ├── src/main/resources/
    │ └── native/
    │ ├── linux/x86_64/libmylib.so
    │ ├── windows/x86_64/mylib.dll
    │ └── macos/aarch64/libmylib.dylib
    ├── native/
    │ ├── mylib.h
    │ ├── mylib.c
    │ └── Makefile
    ├── build.gradle / pom.xml
    └── README.md


    总结

    Project Panama 的 Foreign Function & Memory API 是 Java 平台在本地互操作领域的重大进步:

    维度改进
    开发效率 纯Java编写,无需C胶水代码,jextract自动生成绑定
    安全性 边界检查、Arena生命周期管理、防止内存泄漏
    性能 调用开销降低5-10倍,零拷贝数据传输,JIT可优化
    可维护性 统一调试、标准工具链、跨平台API

    适用场景:调用C/C++/Rust库、高性能计算、系统API访问、遗留代码集成。

    JDK版本要求:JDK 22+(正式API),无需任何预览标志或额外模块。

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » Java深入解析篇四十四之Project Panama详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址