Chapter sebelumnya memanggil strcmp dan qsort lewat defaultLookup — jalur cepat untuk simbol pustaka standar yang sudah termuat. Aplikasi nyata biasanya butuh lebih dari itu: pustaka basis data, codec, atau kriptografi bawaan sistem operasi yang harus dimuat berdasarkan nama berkasnya. FFM menyediakan SymbolLookup sebagai mekanisme pemuatannya, dan desainnya sengaja mengikat masa hidup pustaka pada Arena: ketika arena ditutup, pustaka turut dilepas. Chapter ini membahas tiga sumber simbol, cara memanggil fungsi dari pustaka yang baru dimuat, dan strategi menghadapi perbedaan nama berkas antar platform — semuanya dengan contoh yang berjalan pada JDK 25.
Tiga Sumber Simbol #
SymbolLookup punya tiga pabrik dengan wilayah kerja berbeda. Pilihan pabrik menentukan simbol mana yang bisa ditemukan dan kapan pustaka dilepas dari memori:
| Pabrik | Sumber simbol | Masa hidup |
|---|---|---|
Linker.defaultLookup() | pustaka standar yang sudah termuat (libc) | ikut JVM |
SymbolLookup.libraryLookup(nama, arena) | pustaka dimuat eksplisit dari nama/berkas | ikut arena — tertutup bersama arena |
SymbolLookup.loaderLookup() | pustaka yang dimuat System.loadLibrary di classloader saat ini | ikut classloader |
Metode find() selalu mengembalikan Optional<MemorySegment> sehingga simbol yang tidak ada cukup ditangani sebagai Optional kosong, bukan NullPointerException. Program berikut memuat libc secara eksplisit, memeriksa keberadaan simbol, memanggil abs, lalu membuktikan bahwa pemuatan nama yang salah gagal sejak awal:
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
public class MuatLibrary {
public static void main(String[] args) throws Throwable {
// Muat libc secara eksplisit; pemuatan terikat masa hidup arena.
try (Arena arena = Arena.ofConfined()) {
SymbolLookup libc = SymbolLookup.libraryLookup("libc.so.6", arena);
System.out.println("strlen ada : " + libc.find("strlen").isPresent());
System.out.println("abs ada : " + libc.find("abs").isPresent());
System.out.println("xyz ada : " + libc.find("simbol_tidak_ada").isPresent());
// Ambil handle abs lalu panggil lewat downcall biasa.
MethodHandle abs = Linker.nativeLinker().downcallHandle(
libc.find("abs").get(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
System.out.println("abs(-42) : " + (int) abs.invoke(-42));
System.out.println("abs(99) : " + (int) abs.invoke(99));
}
// Nama pustaka yang salah gagal saat dimuat, bukan saat dipanggil.
try (Arena arena = Arena.ofConfined()) {
SymbolLookup salah = SymbolLookup.libraryLookup("libtidakada.so", arena);
System.out.println("baris ini tidak tercapai");
} catch (IllegalArgumentException e) {
System.out.println("galat muat : " + e.getMessage());
}
}
}Output yang Dihasilkan:
strlen ada : true
abs ada : true
xyz ada : false
abs(-42) : 42
abs(99) : 99
galat muat : Cannot open library: libtidakada.soMari kita telaah baris-baris pentingnya:
- Baris
SymbolLookup.libraryLookup("libc.so.6", arena): JVM membuka berkas pustaka dengan nama persis itu — tidak ada prefiks atau sufiks yang ditambahkan otomatis; nama harus lengkap sesuai platform. - Baris
libc.find("simbol_tidak_ada").isPresent(): simbol yang tidak dikenal tidak melempar exception, melainkanOptionalkosong — program boleh menyediakan jalur cadangan (misalnya implementasi murni Java) tanpatry/catch. - Baris
FunctionDescriptor.of(JAVA_INT, JAVA_INT): signatureint abs(int)tidak memakai pointer sama sekali, jadi pemetaannya langsungintkeinttanpaMemorySegment. - Blok
catch (IllegalArgumentException e): kegagalan membuka berkas pustaka terjadi di titik pemuatan, dengan pesan yang menyebut nama berkasnya — kontras dengan JNI yang baru karam saatUnsatisfiedLinkErrormuncul di pemanggilan pertama.
Pustaka Lawas dan Nama Antar Platform #
Untuk pustaka yang sudah dimuat lewat mekanisme JNI lawas, loaderLookup mengambil simbolnya tanpa memuat ulang:
static {
System.loadLibrary("audio"); // mencari libaudio.so di java.library.path
}
SymbolLookup dariLoader = SymbolLookup.loaderLookup();
MethodHandle rms = Linker.nativeLinker().downcallHandle(
dariLoader.find("Java_PustakaAudio_hitungRms").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS, ValueLayout.JAVA_INT));Nama berkas pustaka berbeda di tiap sistem operasi, dan libraryLookup tidak menerjemahkannya. Ketiga varian berikut merujuk pustaka yang sama:
| Platform | Nama berkas lazim |
|---|---|
| Linux | libsqlite3.so atau libsqlite3.so.0 |
| macOS | libsqlite3.dylib |
| Windows | sqlite3.dll |
Karena itu program portabel memilih nama saat runtime. Pola yang umum: deteksi os.name dan os.arch, lalu susun nama berkasnya — nilai keduanya stabil per platform sehingga pemilihan pun deterministik. Alternatif yang lebih kokoh: letakkan berkas pustaka di dalam distribusi aplikasi dan muat lewat Path absolut hasil Path.of(appDir, namaBerkas) — libraryLookup juga menerima Path, bukan hanya String.
Satu disiplin yang membedakan FFM dari JNI tetap perlu dijaga: simbol yang diambil dari arena tertutup tidak boleh dipakai lagi. Setelah arena.close() berjalan, segmen hasil find() menolak akses dengan IllegalStateException — kegagalan yang terdeteksi, bukan kerusakan senyap. Dengan pemuatan pustaka terkendali, kita sudah menguasai interop di dalam proses yang sama; chapter berikutnya beralih ke jalur yang sama sekali berbeda: meluncurkan program eksternal sebagai proses terpisah lewat ProcessBuilder.
Referensi #
- Oracle: SymbolLookup API
- Oracle: Package java.lang.foreign
- OpenJDK: JEP 454 — Foreign Function & Memory API
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
MuatLibrary.java
Unduh berkasimport java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
public class MuatLibrary {
public static void main(String[] args) throws Throwable {
// Muat libc secara eksplisit; pemuatan terikat masa hidup arena.
try (Arena arena = Arena.ofConfined()) {
SymbolLookup libc = SymbolLookup.libraryLookup("libc.so.6", arena);
System.out.println("strlen ada : " + libc.find("strlen").isPresent());
System.out.println("abs ada : " + libc.find("abs").isPresent());
System.out.println("xyz ada : " + libc.find("simbol_tidak_ada").isPresent());
// Ambil handle abs lalu panggil lewat downcall biasa.
MethodHandle abs = Linker.nativeLinker().downcallHandle(
libc.find("abs").get(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
System.out.println("abs(-42) : " + (int) abs.invoke(-42));
System.out.println("abs(99) : " + (int) abs.invoke(99));
}
// Nama pustaka yang salah gagal saat dimuat, bukan saat dipanggil.
try (Arena arena = Arena.ofConfined()) {
SymbolLookup salah = SymbolLookup.libraryLookup("libtidakada.so", arena);
System.out.println("baris ini tidak tercapai");
} catch (IllegalArgumentException e) {
System.out.println("galat muat : " + e.getMessage());
}
}
}