Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java Goes Outside: FFM API, Vector API, and Beyond

Java Goes Outside: FFM API, Vector API, and Beyond

The chinese version slide for JCConf 2026. Translated by AI.

Avatar for Sugiyama Takaaki

Sugiyama Takaaki

September 10, 2026

More Decks by Sugiyama Takaaki

Other Decks in Programming

Transcript

  1. JCConf TAIWAN 2026 / JAVA COMMUNITY CONFERENCE Java Goes Outside

    FFM API, Vector API, and Beyond Takaaki Sugiyama / 杉山 貴章 @zinbe
  2. 講者 介紹 關於我 • Takaaki Sugiyama • @zinbe / @zinbe.bsky.social

    • 軟體工程師 • 技術作家/作者 • Japan Java User Group (JJUG) 幹事 • CCC Association 理事
  3. 議程 今天要談的內容 • 對 Java 來說,「走出去」是什麼意思 • FFM API •

    原生程式碼與記憶體 • Vector API • CPU 的 SIMD 指令 • Project Babylon • GPU 與加速器
  4. Ja va 為何 走出 去 「外面的世界」= JVM 之外 • 「Write

    Once, Run Anywhere」— Java 最初的承諾 • 所有平台相依的東西,都藏在 JVM 這層抽象之下 • 那條界線之外的一切,就是「外面的世界」 Java 應用程式 JVM 「隱藏」曾是 Java 的強項 OS · CPU · 硬體 界線以下,全部隱藏 外面
  5. JN I 的時 代 JNI – Java Native Interface •

    從 Java 呼叫原生程式碼(C/C++),也能從原生程式碼呼叫 Java 的 API • 讓 Java 能存取 OS 專屬的功能 • 例如序列埠 • 從 Java 1.1(1997 年)就已經存在
  6. JN I 的時 代 用 Java 和 Arduino 讓 LED

    閃爍 ── JNI 版 Java 主程式 C 標頭檔 序列通訊的 介面 C 原生函式庫 C 函式的 包裝類別 OS 系統呼叫 Arduino Sckech
  7. JN I 的時 代 Arduino 草稿碼 Led_blink.ino const int LED_PIN

    = 18; // Use GPIO 18 void loop() { if (Serial.available() > 0) { char command = Serial.read(); if (command == ‘1’) { digitalWrite(LED_PIN, HIGH); } else if (command == '0’) { digitalWrite(LED_PIN, LOW); } } }
  8. JN I 的時 代 包裝類別與 C 標頭檔 SerialPortNative.java SerialPortNative.h(自動產生) public

    class SerialPortNative { static { System.loadLibrary("serialport"); } #include <jni.h> 載入 C 函式庫 public native int openPort (String portName); public native int closePort(int fd); public native int writeData (int fd, byte[] data, int length); } 系統呼叫的包裝(open/close/write) JNI 用的 C 標頭檔 #ifndef _Included_SerialPortNative #define _Included_SerialPortNative JNIEXPORT jint JNICALL Java_SerialPortNative_openPort (JNIEnv *, jobject, jstring); JNIEXPORT jint JNICALL Java_SerialPortNative_closePort (JNIEnv *, jobject, jint); JNIEXPORT jint JNICALL Java_SerialPortNative_writeData (JNIEnv *, jobject, jint, jbyteArray, jint); #endif javac -h OUTPUT_DIR SerialPortNative.java
  9. JN I 的時 代 C 原生函式庫 SerialPortNative.c #include "SerialPortNative.h" JNIEXPORT

    jint JNICALL Java_SerialPortNative_openPort (JNIEnv *env, jobject obj, jstring portName) { const char *port = (*env)->GetStringUTFChars(env, portName, NULL); int fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK); // Setting for serial communication return fd; gcc -shared -fPIC ¥ -I“$JAVA_HOME/include” ¥ -I”$JAVA_HOME/include/darwin” ¥ -o libserialport.dylib \ SerialPortNative.c } JNIEXPORT jint JNICALL Java_SerialPortNative_closePort (JNIEnv *env, jobject obj, jint fd) { ... } JNIEXPORT jint JNICALL Java_SerialPortNative_writeData (JNIEnv *env, jobject obj, jint fd, jbyteArray data, jint length) { ... } libserialport.dylib
  10. JN I 的時 代 序列通訊的介面 SerialPort.java public class SerialPort implements

    AutoCloseable { private final SerialPortNative nativePort; private final int fd; public SerialPort(String portName) throws Exception { this.nativePort = new SerialPortNative(); this.fd = nativePort.openPort(portName); } public void sendCommand(char command) { byte[] data = new byte[] { (byte) command }; nativePort.writeData(fd, data, 1); } public void close() { nativePort.closePort(fd); } } 開啟連接埠 寫入資料 關閉連接埠
  11. JN I 的時 代 Java 主程式 LedBlinker.java public class LedBlinker

    implements AutoCloseable { private final SerialPort serialPort; public LedBlinker(String portName) throws Exception { this.serialPort = new SerialPort(portName); 連線 } public void blink(int times, int delayMs) throws InterruptedException { for (int i = 0; i < times; i++) { serialPort.sendCommand(‘1’); 點亮 Thread.sleep(delayMs); serialPort.sendCommand(‘0’); 熄滅 Thread.sleep(delayMs); } } public static void main(String[] args) { String port = "/dev/cu.usbserial-110"; try (LedBlinker blinker = new LedBlinker(port)) { // 5times,500ms blinker.blink(5, 500); } catch (Exception e) { e.printStackTrace(); } public void close() { serialPort.close(); } } }
  12. JN I 的時 代 ……這實在太麻煩了 實作步驟複雜 必須具備 C 的知識 沒有型別安全

    記憶體要自己管理 我只是想跟序列埠說句話而已……
  13. Ja va 為何 走出 去 為什麼這麼複雜? I can't remember who

    it was, but I was talking to one of the engineers back in the Sun days and expressing my frustration with JNI being hard to use. That's when he said it had been developed that way to encourage people NOT to use it… — Simon Ritter, Deputy CTO of Azul 難用是刻意設計的結果,不是意外
  14. Ja va 為何 走出 去 時代變了 現在外面有些什麼 原生函式庫 SIMD GPU

    · TPU · FPGA 龐大的既有程式資產 ── 影 像處理、機器學習、密碼學、 數值計算 CPU 自己的指令。AVX-512 一次可處理 16 個 float 平行運算早已不是特殊領域 用不到它們,已經不再是換取安全的代價
  15. Ja va 為何 走出 去 Java 選擇的路 1 2 3

    藏在抽象之下 效能與表達力都要犧牲 交給其他語言處理 這就是 JNI 的世界。複雜度依然存在 讓外面的世界能用 Java 表達 Java 選擇的路
  16. Ja va 為何 走出 去 本場議程的地圖 外面的世界 Java 的答案 成為

    Java 表達的東西 原生程式碼與記憶體 FFM API 記憶體與函式呼叫 CPU 的 SIMD 指令 Vector API 向量運算 GPU 與加速器 Project Babylon 程式碼本身
  17. https://openjdk.org/jeps/454 FFM API 什麼是 FFM API • 讓 Java 安全又高效地操作原生記憶體與原生函式的

    API • 在 Java 22(2024 年 3 月)正式發布 • OpenJDK Project Panama 的成果 目標 • 用真正好用的東西取代 JNI • 降低額外開銷 • 安全地管理原生記憶體
  18. FFM API FFM API 的組成 Foreign Memory API Foreign Function

    API 安全地管理原生記憶體 呼叫原生函式 (取代 JNI) 主要功能 • Arena • MemorySegment • MemoryLayout 主要功能 • SymbolLookup • Linker • FunctionDescriptor
  19. FFM API ・記 憶體 Arena ── 管理記憶體的生命週期 • Arena 具有作用域,從中配置的每一個

    segment 都會綁在這個作用域上 try (Arena arena = Arena.ofConfined()) { // All memory allocated // here becomes // inaccessible once the // scope is left • 記憶體何時釋放、哪些執行緒可以存取,都由 Arena 決定 • 共有四種:Global/Automatic/Confined/ Shared }
  20. FFM API ・記 憶體 MemorySegment ── 一塊原生記憶體區域 // Allocate memory

    MemorySegment segment = arena.allocate(100); // Write data segment.set(ValueLayout.JAVA_INT, 0, 42); segment.set(ValueLayout.JAVA_BYTE, 4, (byte) 5); // Read data int value = segment.get(ValueLayout.JAVA_INT, 0); • 越界存取會拋出例外 • Arena 一旦關閉,該 segment 會自動變得無法 存取 在 C 裡,這些是 segfault ── 或者更糟,是看起來還能跑的程式
  21. FFM API ・記 憶體 MemoryLayout ── 記憶體中資料的形狀 C Java struct

    Point { int x; int y; }; StructLayout pointLayout = MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName("x"), ValueLayout.JAVA_INT.withName("y") ); • ValueLayout ── 基本型別(JAVA_INT、JAVA_LONG…) • StructLayout ── 結構體 • SequenceLayout ── 同一個 layout 的重複
  22. FFM API ・函 式 Foreign Function API 的三個要件 • SymbolLookup:在函式庫中尋找函式的位址

    • defaultLookup():搜尋標準 C 函式庫(libc) • libraryLookup():搜尋指定路徑下的函式庫 • loaderLookup():搜尋以 System.loadLibrary() 載入的函式庫 • Linker:把原生函式的位址轉換成 MethodHandle • downcallHandle(): 從 Java 呼叫原生函式 • upcallStub():把回呼函式傳給原生程式碼 • FunctionDescriptor:定義原生函式的簽章資訊
  23. FFM API ・函 式 範例:呼叫 strlen() Linker linker = Linker.nativeLinker();

    SymbolLookup stdlib = linker.defaultLookup(); MethodHandle strlen = linker.downcallHandle( stdlib.find("strlen").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)); try (Arena arena = Arena.ofConfined()) { MemorySegment str = arena.allocateFrom("Hello"); long len = (long) strlen.invokeExact(str); }
  24. FFM API ・函 式 範例:呼叫 strlen() Linker linker = Linker.nativeLinker();

    SymbolLookup stdlib = linker.defaultLookup(); MethodHandle strlen = linker.downcallHandle( stdlib.find("strlen").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)); try (Arena arena = Arena.ofConfined()) { MemorySegment str = arena.allocateFrom("Hello"); long len = (long) strlen.invokeExact(str); } 還是有點複雜
  25. FFM API ・工 具 https://jdk.java.net/jextract/ jextract • 從 C 標頭檔機械式地產生

    Java 繫結 • Project Panama 的另一項成果 • 目前是 early-access 版本,需要另外安裝 jextract --output classes -t org.unix /usr/include/string.h ↓ public static long strlen(MemorySegment __s) { ...... } long length = (long) org.unix.string_h.strlen(str);
  26. FFM API ・生 態系 https://www.pi4j.com/about/info-v4/ 另一個例子:Raspberry Pi • 透過 GPIO

    直接控制硬體 • 用 FFM API 呼叫 C 函式庫 libgpiod Pi4J V4 · 2026 年 2 月 • 從 Java 操作 GPIO、I2C、SPI、PWM 的標準函式 庫 • V4 改以 Java 25 為基礎,並加入使用 FFM API 的 外掛 • 原生函式庫是 Pi4J 的心臟 ── 而這顆心臟現在換 成了 FFM provider 可以外掛式抽換 PiGpio 以 JNI 為基礎 GpioD 以 libgpiod 為基礎 LinuxFS 以檔案系統為基礎 FFM 以 FFM API 為基礎
  27. FFM API ・安 全性 https://openjdk.org/jeps/8305968 FFM API 真的安全嗎? 執行時會出現警告 •

    JEP draft: Integrity by Default ── 對不安全的功能預設停用或提出警告 • FFM API 有些方法本質上就不安全 • Linker.downcallHandle, SymbolLookup.libraryLookup, … • JNI、sun.misc.Unsafe、以及部分反射機制同樣不安全 走到外面,必須取得明確的許可
  28. VE CTO R A PI 再看一次地圖 外面的世界 Java 的答案 成為

    Java 表達的東西 原生程式碼與記憶體 FFM API 記憶體與函式呼叫 CPU 的 SIMD 指令 Vector API 向量運算 GPU 與加速器 Project Babylon 程式碼本身
  29. VE CTO R A PI SIMD 一道指令,處理多筆資料 純量 a[0]+b[0] →

    a[1]+b[1] → a[2]+b[2] → a[3]+b[3] 4 次運算 SIMD a[0..3] + b[0..3] 1 次運算 SSE 4 個 float AVX 8 個 float AVX-512 16 個 float CPU 一直都有這個能力,只是 Java 沒有明確使用它的方法 ARM SVE 最多 16 個
  30. https://openjdk.org/jeps/537 VE CTO R A PI 什麼是 Vector API 在執行期編譯成

    CPU 的 SIMD 指令 核心概念 VectorSpecies Vector<E> 元素型別與向量長度的組合。 FloatVector.SPECIES_PREFERRED 會挑選最 適合執行環境的組合 固定長度的元素序列: FloatVector、IntVector、DoubleVector、 ByteVector… Lane VectorMask 向量中每個元素所在的位置 以 lane 為單位,控制運算是否套用
  31. VE CTO R A PI 為什麼自動向量化還不夠 HotSpot 確實會自動向量化。不過 ── •

    JIT 會分析迴圈,自動轉換成 SIMD 指令 • 但它什麼時候會生效,並沒有保證 • 結果會隨迴圈的寫法、JVM 版本與 CPU 而改變 • 複雜的演算法根本不會被辨識出來 • 向量化的 hashCode、特殊的陣列比較等等 「應該會變快」 → 「一定會變快」
  32. VE CTO R A PI 程式範例:內積 純量版 Vector API 版

    float sum = 0f; for (int i = 0; i < a.length; i++) { sum += a[i] * b[i]; } static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED; FloatVector vsum = FloatVector.zero(SPECIES); int upperBound = SPECIES.loopBound(a.length); for (int i = 0; i < upperBound; i += SPECIES.length()) { var va = FloatVector.fromArray(SPECIES, a, i); var vb = FloatVector.fromArray(SPECIES, b, i); vsum = va.fma(vb, vsum); // vsum += va * vb } float sum = vsum.reduceLanes(VectorOperators.ADD); // tail elements handled separately 程式碼確實變長了。換來的是,你寫什麼指令就得到什麼指令
  33. VE CTO R A PI 在哪裡派得上用場 只要是對大量資料重複相同運算的地方 • 機器學習 •

    影像處理 • 線性代數 • 密碼學 • 金融計算 • JDK 自身的內部實作
  34. VE CTO R A PI 仍在 incubator 階段 • 在

    JDK 16(2021 年 3 月)導入 • JDK 26 的 JEP 529 是第 11 次 incubator • JDK 27 預定推出 JEP 537 ── 第 12 次 incubator • 使用時需要 --add-modules jdk.incubator.vector Vector API 會持續 incubate,直到 Value Objects(Project Valhalla 的功能)以 preview 形式提供為止 JEP 401: Value Objects (Preview) 以 JDK 28(預計 2027 年 3 月)為目標
  35. D EM O · V EC T OR AP I

    純量版 for (int px = 0; px < width; px++) { float cx = cxs[px]; float x = 0f, y = 0f; int iter = 0; while (x * x + y * y <= 4f && iter < maxIter) { float xt = x * x - y * y + cx; y = (x * y) * 2f + cy; x = xt; iter++; } iters[px] = iter; } 這個迴圈要跑幾次 取決於資料本身 • 每個像素發散的次數都不一樣 • JIT 無法事先知道迴圈次數 • 這正是自動向量化處理不了的情 況
  36. D EM O · V EC T OR AP I

    向量版:整體架構 for (int px = 0; px < width; px += SPECIES.length()) { FloatVector cx = FloatVector.fromArray(SPECIES, cxs, px); FloatVector cy = FloatVector.broadcast(SPECIES, cyScalar); FloatVector x = FloatVector.zero(SPECIES); FloatVector y = FloatVector.zero(SPECIES); FloatVector iter = FloatVector.zero(SPECIES); for (int i = 0; i < maxIter; i++) { // the same arithmetic, on whole vectors // + three lines that do the real work } iter.intoArray(iterCounts, px); } • 外層迴圈每次前進 SPECIES.length() 個像素 • while 變成了 for ── 固定 次數,最多跑到 maxIter • 「這個像素何時停止?」從迴 圈條件裡移了出去
  37. D EM O · V EC T OR AP I

    向量版:遮罩(mask) VectorMask<Float> active = x2.add(y2).lt(4f); if (!active.anyTrue()) break; iter = iter.add(1f, active);
  38. D EM O · V EC T OR AP I

    實測數字 時間 加速比 純量 2685 ms 1.0× Vector API / ARM NEON – 4 lanes 775 ms 3.46× Vector API / AVX-512 – 16 lanes 288 ms 9.32× 為什麼加速比不等於 lane 數? 提早發散的 lane 仍要等到同一組裡最後一個 lane 結束。每一組都得為最慢的那個像素付出代價 這不就是平行運算嗎? 不是 ── 這是單一執行緒、單一核心。SIMD 是「寬度」,不是執行緒數量。而且兩者可以疊加
  39. PR O JE CT B AB YL ON 再看一次地圖 外面的世界

    Java 的答案 成為 Java 表達的東西 原生程式碼與記憶體 FFM API 記憶體與函式呼叫 CPU 的 SIMD 指令 Vector API 向量運算 GPU 與加速器 Project Babylon 程式碼本身
  40. PR O JE CT B AB YL ON https://openjdk.org/projects/babylon/ 什麼是

    Project Babylon • 讓 Java 延伸到「外部程式設計模型」的專案 • GPU、機器學習模型、SQL、可微分程式設計 Code Model Code Reflection API 由編譯器產生、層級高於位元組碼的符號 化程式碼表示 在執行期存取、操作與轉換 Code Model 的 API 還不是 JEP ── 現階段優先把 code reflection 設計對
  41. PR O JE CT B AB YL ON Code Reflection

    的新意在哪裡 @CodeReflection static float compute(float a, float b) { return a * b + 1.0f; } • 傳統的反射:檢視結構 ── 類別、方法、欄位 • Code Reflection:把運算的意義擷取成符號化的表示 位元組碼 Code Model 「要怎麼執行」 「在計算什麼」
  42. PR O JE CT B AB YL ON HAT —

    Heterogeneous Accelerator Toolkit Java → Code Model → GPU 驅動程式二進位檔 • 後端:OpenCL C、CUDA PTX(SPIR-V 開發中) • NDRange API(執行緒配置)、iFaceMapper(記憶體佈局)、Accelerator (裝置選擇) 不寫 C 不需 kernel 原始檔 不用 JNI
  43. PR O JE CT B AB YL ON 不只是 GPU

    在 GPU 上執行 生成式 AI 推論 HAT 在 Java 中執行 ONNX 模型 自動微分 查詢轉換 訓練神經網路 在 Java 裡做出類似 C# LINQ 的東西 仍在實驗階段 ── 現在就能用 --add-modules jdk.incubator.code 試試
  44. 總結 完成版的地圖 外面的世界 Java 的答案 成為 Java 表達的東西 狀態 原生程式碼

    與記憶體 FFM API 記憶體與 函式呼叫 Java 22 正式版 CPU 的 SIMD 指令 Vector API 向量運算 incubator 階段, 等待 Valhalla GPU 與加速器 Project Babylon 程式碼本身 實驗階段, 尚未成為 JEP 完成的只有 FFM API。我們還在半路上
  45. 總結 重點回顧 Java 選擇的,是用自己的語言來談論外面的世界 FFM API Java 的原生互通已經徹底改變。 如同 Pi4J

    V4 所展現的,生態系也開始動了 Vector API 讓 CPU 的能力能從 Java 明確地使用。Valhalla 的進展可能會改變 現況 Project Babylon 正試圖擴大 Java 本身能觸及的範圍 我們還在半路上。而它會繼續變得更好
  46. Java 正在踏出 JVM 之外。 To boldly go where Java has

    never gone before. 勇踏 Java 前所未至之境 謝謝
  47. 總結 參考資料 • OpenJDK — https://openjdk.org/ • JEP 454: Foreign

    Function & Memory API — https://openjdk.org/jeps/454 • JEP 472: Prepare to Restrict the Use of JNI — https://openjdk.org/jeps/472 • JEP 529: Vector API (Eleventh Incubator) — https://openjdk.org/jeps/529 • JEP 401: Value Objects (Preview) — https://openjdk.org/jeps/401 • JEP draft: Integrity by Default — https://openjdk.org/jeps/8305968 • Project Babylon — https://openjdk.org/projects/babylon/ • jextract — https://jdk.java.net/jextract/ • Pi4J — https://www.pi4j.com/