JNI menuntut berkas header hasil generasi dan kode C yang rawan kesalahan memori — beban yang dibahas pada chapter sebelumnya. Foreign Function and Memory API (FFM API) menghapus kedua syarat itu: fungsi native dipanggil langsung dari Java sebagai MethodHandle, dan argumennya berpindah lewat MemorySegment yang masa hidupnya dijaga Arena. API ini difinalkan pada Java 22 melalui JEP 454 setelah seri incubator dan preview yang panjang (JEP 191, lalu 442). Chapter Foreign Function and Memory API pada kelas Modern Java sudah membahas sisi memori — MemorySegment, Arena, dan alokasi off-heap — jadi fokus kita sekarang adalah sisi interop: panggilan turun (downcall) ke fungsi native, panggilan naik (upcall) dari native kembali ke Java, dan pemetaan tipe antara dua dunia. Seluruh contoh berjalan pada JDK 25 tanpa flag tambahan.
Panggilan Turun dengan Linker #
Panggilan turun adalah arah Java menuju native. Bahan bakunya ada empat: Linker.nativeLinker() menyediakan jembatan khusus platform, SymbolLookup mencari alamat simbol (misalnya strcmp di libc), FunctionDescriptor menyatakan signature fungsi dengan ValueLayout, dan hasilnya menjadi MethodHandle biasa yang bisa di-invoke(). Pemetaan tipe C ke FFM mengikuti tabel berikut:
| Tipe C | Layout FFM | Tipe Java di MethodHandle |
|---|---|---|
int | JAVA_INT | int |
long, size_t | JAVA_LONG | long |
char* / pointer | ADDRESS | MemorySegment |
void (nilai balik) | FunctionDescriptor.ofVoid(...) | tidak ada hasil |
Program berikut memanggil strcmp dari libc — fungsi yang membandingkan dua string C dan mengembalikan bilangan bertanda:
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
public class BandingkanKata {
public static void main(String[] args) throws Throwable {
// int strcmp(const char *a, const char *b) dari libc.
Linker linker = Linker.nativeLinker();
MethodHandle strcmp = linker.downcallHandle(
linker.defaultLookup().find("strcmp").get(),
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS, ValueLayout.ADDRESS));
try (Arena arena = Arena.ofConfined()) {
MemorySegment apel = arena.allocateFrom("apel");
MemorySegment jeruk = arena.allocateFrom("jeruk");
int a = (int) strcmp.invoke(apel, jeruk);
int b = (int) strcmp.invoke(jeruk, apel);
int c = (int) strcmp.invoke(apel, apel);
System.out.println("strcmp(\"apel\", \"jeruk\") = " + a);
System.out.println("strcmp(\"jeruk\", \"apel\") = " + b);
System.out.println("strcmp(\"apel\", \"apel\") = " + c);
System.out.println("tanda hasil: " + Integer.signum(a) + ", "
+ Integer.signum(b) + ", " + Integer.signum(c));
}
}
}Output yang Dihasilkan:
strcmp("apel", "jeruk") = -9
strcmp("jeruk", "apel") = 9
strcmp("apel", "apel") = 0
tanda hasil: -1, 1, 0Mari kita telaah baris-baris kuncinya:
- Baris
linker.defaultLookup().find("strcmp").get():defaultLookupmencari simbol di pustaka standar yang sudah termuat; hasilnyaOptional, lalu.get()mengambil alamat simbolnya. - Baris
FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS): nilai balikintdinyatakan duluan, diikuti dua parameter pointer — persis mengikuti signatureint strcmp(const char*, const char*). - Baris
arena.allocateFrom("apel"): string Java disalin ke memori native lengkap dengan penghenti nol versi C, sehingga aman dibacastrcmp. - Baris
Integer.signum(a): glibc mengembalikan selisih karakter pertama yang berbeda ('a' - 'j' = -9), jadi program menormalkan hasilnya menjadi tanda-1,0,1— konvensi yang stabil untuk perbandingan.
Panggilan Naik dengan Upcall Stub #
Panggilan naik adalah arah sebaliknya: native memanggil Java. Banyak pustaka C meminta function pointer — qsort misalnya, yang mengurutkan array memakai pembanding buatan pemanggil. FFM menjembatani lewat upcallStub: method Java dibungkus menjadi alamat fungsi yang bisa diterima pihak native, dengan masa hidup terikat arena. Program berikut mengurutkan array int di memori native dengan qsort, sementara pembandingnya ditulis dalam Java:
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
public class UrutNative {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup libc = linker.defaultLookup();
// void qsort(void *base, size_t nmemb, size_t size,
// int (*compar)(const void *, const void *));
MethodHandle qsort = linker.downcallHandle(
libc.find("qsort").get(),
FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS));
// Deskriptor fungsi pembanding: pointer dengan target layout int.
FunctionDescriptor deskriptorBanding = FunctionDescriptor.of(JAVA_INT,
ADDRESS.withTargetLayout(JAVA_INT),
ADDRESS.withTargetLayout(JAVA_INT));
MethodHandle pembanding = MethodHandles.lookup().findStatic(
UrutNative.class, "bandingkan", deskriptorBanding.toMethodType());
try (Arena arena = Arena.ofConfined()) {
int[] data = {42, 7, 19, 3, 88, 1, 56, 23};
MemorySegment larik = arena.allocateFrom(JAVA_INT, data);
// Upcall stub: method Java dibungkus menjadi function pointer.
MemorySegment fungsiBanding = linker.upcallStub(
pembanding, deskriptorBanding, arena);
qsort.invoke(larik, (long) data.length, JAVA_INT.byteSize(),
fungsiBanding);
System.out.println("sebelum : " + Arrays.toString(data));
System.out.println("sesudah : " + Arrays.toString(larik.toArray(JAVA_INT)));
}
}
// Dipanggil dari dalam qsort lewat upcall stub.
static int bandingkan(MemorySegment a, MemorySegment b) {
return Integer.compare(a.get(JAVA_INT, 0L), b.get(JAVA_INT, 0L));
}
}Output yang Dihasilkan:
sebelum : [42, 7, 19, 3, 88, 1, 56, 23]
sesudah : [1, 3, 7, 19, 23, 42, 56, 88]Bedah baris-per-baris untuk bagian upcall-nya:
- Baris
FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS): signatureqsort— pointer array, jumlah anggota, ukuran elemen, dan function pointer pembanding;size_tdipetakan keJAVA_LONG. - Baris
ADDRESS.withTargetLayout(JAVA_INT): parameter pembanding adalah pointer ke elemenint; target layout memberi tahu FFM bahwa segment yang masuk boleh dibaca sebagaiintsehinggabandingkancukup memanggilget(JAVA_INT, 0L)tanpa konversi tambahan. - Baris
MethodHandles.lookup().findStatic(...): method handle untuk method Javabandingkandicari lewat refleksi ringan denganMethodTypeyang dibangun langsung dari deskriptor fungsi. - Baris
linker.upcallStub(pembanding, deskriptorBanding, arena): stub dibuat dan masa hidupnya diikat arena — begitu arena ditutup, alamat stub tidak valid lagi, mencegah native menyimpan pointer yang sudah mati. - Baris
qsort.invoke(...): satu panggilan lintas batas untuk seluruh pengurutan; selama eksekusi,qsortmemanggil kembalibandingkanberkali-kali lewat stub — dua arah bekerja dalam satu operasi.
Dibandingkan JNI, tidak ada satu baris C yang kita tulis: signature dinyatakan dalam Java, data berpindah lewat segmen yang terlindungi batas dan masa hidup, dan kesalahan akses melempar exception alih-alih merusak JVM. Pertanyaan berikutnya: bagaimana memuat pustaka native tertentu — bukan sekadar libc bawaan — dan mencari simbol di dalamnya? Jawabannya ada di chapter Memanggil Library Native.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
BandingkanKata.java
Unduh berkasimport java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
public class BandingkanKata {
public static void main(String[] args) throws Throwable {
// int strcmp(const char *a, const char *b) dari libc.
Linker linker = Linker.nativeLinker();
MethodHandle strcmp = linker.downcallHandle(
linker.defaultLookup().find("strcmp").get(),
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS, ValueLayout.ADDRESS));
try (Arena arena = Arena.ofConfined()) {
MemorySegment apel = arena.allocateFrom("apel");
MemorySegment jeruk = arena.allocateFrom("jeruk");
int a = (int) strcmp.invoke(apel, jeruk);
int b = (int) strcmp.invoke(jeruk, apel);
int c = (int) strcmp.invoke(apel, apel);
System.out.println("strcmp(\"apel\", \"jeruk\") = " + a);
System.out.println("strcmp(\"jeruk\", \"apel\") = " + b);
System.out.println("strcmp(\"apel\", \"apel\") = " + c);
System.out.println("tanda hasil: " + Integer.signum(a) + ", "
+ Integer.signum(b) + ", " + Integer.signum(c));
}
}
}
UrutNative.java
Unduh berkasimport java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
public class UrutNative {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup libc = linker.defaultLookup();
// void qsort(void *base, size_t nmemb, size_t size,
// int (*compar)(const void *, const void *));
MethodHandle qsort = linker.downcallHandle(
libc.find("qsort").get(),
FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS));
// Deskriptor fungsi pembanding: pointer dengan target layout int.
FunctionDescriptor deskriptorBanding = FunctionDescriptor.of(JAVA_INT,
ADDRESS.withTargetLayout(JAVA_INT),
ADDRESS.withTargetLayout(JAVA_INT));
MethodHandle pembanding = MethodHandles.lookup().findStatic(
UrutNative.class, "bandingkan", deskriptorBanding.toMethodType());
try (Arena arena = Arena.ofConfined()) {
int[] data = {42, 7, 19, 3, 88, 1, 56, 23};
MemorySegment larik = arena.allocateFrom(JAVA_INT, data);
// Upcall stub: method Java dibungkus menjadi function pointer.
MemorySegment fungsiBanding = linker.upcallStub(
pembanding, deskriptorBanding, arena);
qsort.invoke(larik, (long) data.length, JAVA_INT.byteSize(),
fungsiBanding);
System.out.println("sebelum : " + Arrays.toString(data));
System.out.println("sesudah : " + Arrays.toString(larik.toArray(JAVA_INT)));
}
}
// Dipanggil dari dalam qsort lewat upcall stub.
static int bandingkan(MemorySegment a, MemorySegment b) {
return Integer.compare(a.get(JAVA_INT, 0L), b.get(JAVA_INT, 0L));
}
}