Úskalí JNA

11. 4. 2014 8:54 zboj

V takzvaných vyšších jazycích založených na bajtkódu a plném GC je často nutné volat nativní kód, ať už kvůli neexistenci knihovny v onom “vyšším” jazyce nebo pro vyšší výkon (z hlediska rychlosti a/nebo zacházením s pamětí). Zatímco .NET má své C++ Interop (dříve IJW; P/Invoke nikdy, ale opravdu nikdy nepoužívejte), Java si dlouho musela vystačit s primitivním JNI. Situaci poněkud zlepšila knihovna JNA, nicméně vzhledem k návrhu celé JVM i zde je hranice mezi oběma světy, narozdíl od .NET, velmi ostrá. Na dvou typických případech ukážu, jak se vyhnout nejčastějším chybám.

Při předání parametru z Javy do C převede JNA automaticky hodnotové typy. Problém může nastat u tříd, jež se předávají jako ukazatel na strukturu. Nativní funkce totiž může používat asynchronní zpracování s následným voláním callbacku. V takovém případě se může stát, že Java objekt zruší, než jej použijeme. A protože není k dispozici obdoba pin_ptr (“přišpendlení objektu na haldě JVM”), nezbývá nic jiného než strukturu explicitně zkopírovat. Celkem se tedy v takovém scénáři zkopíruje dvakrát (kontrolní otázka: proč?). Ono i pin_ptr má v tomto ohledu nevýhodu, přišpendlením objektu (jemuž se v .NET nelze vyhnout, neboť pokud by GC objekt přesunul, zneplatnil by tak ukazatel) totiž celkem efektivně zabráníme GC v práci, protože nebude schopen provádět kompaktizaci haldy.

Druhým případem je předání návratové hodnoty referencí, až už jde o řetězec, pole nebo jakoukoliv třídu. Z céčka můžeme sice přímo vrátit ukazatel, jenže pokud příslušnou paměť později neuvolníme, máme memory leak jak vyšitý. Pochopitelně bychom mohli JNA zavolat znovu a paměť uvolnit, jenže JNA má svou režii, tj. zbytečných volání nativní knihovny se chceme vyhnout. Řešení je naštěstí jednoduché. Místo Neco* funkce(…) použijeme void funkce(…, CallbackFunkce cb), tj. místo vrácení ukazatele jej předáme callbacku a až po něm paměť uvolníme (místo return x; máme cb(x); free(x);). V Javě 8 se jako callback dá využít lambda. Nevýhodou takovéhoto řešení je menší přehlednost kódu v Javě (mohou vznikat vnořené callbacky). Alternativou je použití autopoolu v céčku (pak můžeme použít přímo return x; a dál se o uvolnění pamětí nestarat, aniž by vznikl memory leak), čímž přesuneme odpovědnost za paměť z JVM na nativní knihovnu.

Sdílet