bigywater.tech
Java · 303

Foreign Function & Memory API:安全地访问 native

不写 JNI 也能调 C?Java 的 Foreign Function & Memory API 怎么描述 C 结构体?越界、关闭后访问会怎样?JDK 21 的预览版和 JDK 27 的正式版又有哪些区别?这篇用真的 C 库把它们跑一遍。

32 / 0,4,8,16,24
C 的 sizeof(Rec) 与各字段偏移,Java 的 StructLayout 与之完全一致
5.6 → 7.4 ns
getpid 下行调用平均耗时(JDK 21 → JDK 27,各一次观察)
4 行 WARNING
JDK 27 不加 --enable-native-access 时输出的警告行数

JDK 21 预览

实测 · 本机执行输出
  1 getpid:27372(Java 里 ProcessHandle.current().pid()=27372)
  2 strlen("Hello, FFM!")=11;往返读回 Hello, FFM!;段大小 12(含结尾的 0)
  3 qsort 用 Java 比较器排序:[1, 3, 5, 7, 9]
  4 C 的 sizeof(Rec)=32,offsetof tag/id/big/d/name=0/4/8/16/24,对齐 8
    Java 的 REC.byteSize()=32,offset id/big/d/name=4/8/16/24,byteAlignment 8
  5 C 填充的结构体在 Java 里读出:tag=7,id=42,big=42000000294,d=10.5,name=rec
  6 C 的 sum_array 对 Java 分配的 int[10]:55
  7 错误:
     越界读(offset 14 读 int,段 16 字节):IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ heapBase: Optional.empty address:523132688992 limit: 16 }; new offset = 14; new length = 4
     未对齐读(offset 4 读 long):IllegalArgumentException: Misaligned access at address: 523132688996
     负偏移:IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ heapBase: Optional.empty address:523132688992 limit: 16 }; new offset = -4; new length = 4
     只读段写入:UnsupportedOperationException: Attempt to write a read-only segment
     Arena 关闭之后访问:IllegalStateException: Already closed
     重复关闭 Arena:IllegalStateException: Already closed
     confined Arena 的段被其它线程访问:WrongThreadException: Attempted access outside owning thread
     shared Arena 的段在其它线程写入后读到:5
     ofAuto Arena(由 GC 回收):读到 3
     ofAuto 不能手动 close:UnsupportedOperationException: Attempted to close a non-closeable session
  8 getpid 下行调用:2000 万次共 112 ms,平均 5.6 ns/次(x=true)

JDK 21 不加预览

实测 · 本机执行输出
  编译失败: <jtmp>/j163a/Main.java:4: error: ValueLayout is a preview API and is disabled by default.
    import static java.lang.foreign.ValueLayout.*;
                                   ^
      (use --enable-preview to enable preview APIs)

JDK 27 无 native-access

实测 · 本机执行输出
  1 getpid:27378(Java 里 ProcessHandle.current().pid()=27378)
  2 strlen("Hello, FFM!")=11;往返读回 Hello, FFM!;段大小 12(含结尾的 0)
  3 qsort 用 Java 比较器排序:[1, 3, 5, 7, 9]
  4 C 的 sizeof(Rec)=32,offsetof tag/id/big/d/name=0/4/8/16/24,对齐 8
    Java 的 REC.byteSize()=32,offset id/big/d/name=4/8/16/24,byteAlignment 8
  5 C 填充的结构体在 Java 里读出:tag=7,id=42,big=42000000294,d=10.5,name=rec
  6 C 的 sum_array 对 Java 分配的 int[10]:55
  7 错误:
     越界读(offset 14 读 int,段 16 字节):IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ kind: native, address: 0x77ea101260, byteSize: 16 }; attempting to access an element of length 4 at offs
     未对齐读(offset 4 读 long):IllegalArgumentException: Target offset 4 is incompatible with alignment constraint 8 (of j8) for segment MemorySegment{ kind: native, address: 0x77ea101260, byteSize: 16 }
     负偏移:IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ kind: native, address: 0x77ea101260, byteSize: 16 }; attempting to access an element of length 4 at offs
     只读段写入:IllegalArgumentException: Attempt to write a read-only segment
     Arena 关闭之后访问:IllegalStateException: Already closed
     重复关闭 Arena:IllegalStateException: Already closed
     confined Arena 的段被其它线程访问:WrongThreadException: Attempted access outside owning thread
     shared Arena 的段在其它线程写入后读到:5
     ofAuto Arena(由 GC 回收):读到 3
     ofAuto 不能手动 close:UnsupportedOperationException: Attempted to close a non-closeable session
  8 getpid 下行调用:2000 万次共 147 ms,平均 7.4 ns/次(x=true)
  WARNING: A restricted method in java.lang.foreign.Linker has been called
  WARNING: java.lang.foreign.Linker::downcallHandle has been called by Main in an unnamed module (file:<jtmp>/j163a/out/)
  WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
  WARNING: Restricted methods will be blocked in a future release unless native access is enabled

JDK 27 有 native-access

实测 · 本机执行输出
  1 getpid:27382(Java 里 ProcessHandle.current().pid()=27382)
  2 strlen("Hello, FFM!")=11;往返读回 Hello, FFM!;段大小 12(含结尾的 0)
  3 qsort 用 Java 比较器排序:[1, 3, 5, 7, 9]
  4 C 的 sizeof(Rec)=32,offsetof tag/id/big/d/name=0/4/8/16/24,对齐 8
    Java 的 REC.byteSize()=32,offset id/big/d/name=4/8/16/24,byteAlignment 8
  5 C 填充的结构体在 Java 里读出:tag=7,id=42,big=42000000294,d=10.5,name=rec
  6 C 的 sum_array 对 Java 分配的 int[10]:55
  7 错误:
     越界读(offset 14 读 int,段 16 字节):IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ kind: native, address: 0x7a1ee19440, byteSize: 16 }; attempting to access an element of length 4 at offs
     未对齐读(offset 4 读 long):IllegalArgumentException: Target offset 4 is incompatible with alignment constraint 8 (of j8) for segment MemorySegment{ kind: native, address: 0x7a1ee19440, byteSize: 16 }
     负偏移:IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ kind: native, address: 0x7a1ee19440, byteSize: 16 }; attempting to access an element of length 4 at offs
     只读段写入:IllegalArgumentException: Attempt to write a read-only segment
     Arena 关闭之后访问:IllegalStateException: Already closed
     重复关闭 Arena:IllegalStateException: Already closed
     confined Arena 的段被其它线程访问:WrongThreadException: Attempted access outside owning thread
     shared Arena 的段在其它线程写入后读到:5
     ofAuto Arena(由 GC 回收):读到 3
     ofAuto 不能手动 close:UnsupportedOperationException: Attempted to close a non-closeable session
  8 getpid 下行调用:2000 万次共 145 ms,平均 7.3 ns/次(x=true)

分步演示

FFM API
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(--enable-preview)与 JDK 27;macOS arm64,C 库用 clang 编译
    • 没有验证:Linux / Windows 的 ABI、变参函数(printf)的调用、结构体按值传递与返回、errno 捕获(Linker.Option.captureCallState)、JDK 22–26 的 API 演变中间形态、jextract 生成绑定、与 JNI 的性能对比(没有测 JNI)
    京ICP备2026067502号-1京公网安备11010502063487号