JNI — Java Virtual Machine ichidagi Java yoki Kotlin kodi bilan C va C++ kabi native kod orasida aloqa o‘rnatadigan standart interfeys. To‘liq nomi Java Native Interface. Androidda JNI NDK orqali build qilingan native kutubxonalarni Kotlin yoki Java application qismi bilan bog‘lashda keng ishlatiladi.
JNI method chaqirish, object yaratish, field o‘qish, array bilan ishlash va exception uzatish imkonini beradi. Biroq u type, memory va thread lifecycle’ini ehtiyotkor boshqarishni talab qiladi.
Native method
Java yoki Kotlin class native method e’lon qiladi:
external fun process(data: ByteArray): Int
Bu methodning implementationi JVM bytecode ichida emas, native library’da joylashadi.
Library oldindan System.loadLibrary bilan yuklanadi.
Nom bo‘yicha bog‘lash
Native function maxsus nomlash qoidasi bilan methodga ulanadi:
Java_package_Class_method
Package va class nomlari encoded qilinadi.
Overloaded methodlar signature suffix talab qilishi mumkin.
Uzun automatic nomlar sabab explicit registration ko‘p loyihada qulayroq.
RegisterNatives
Native methodlarni runtime’da jadval orqali ro‘yxatdan o‘tkazish mumkin.
Jadval:
ni saqlaydi.
Bu symbol nomini soddalashtiradi va obfuscation bilan ishlashni nazorat qilishga yordam beradi.
JNIEnv
Har native JNI function JNIEnv* pointer oladi.
U JVM operationlarini chaqirish uchun function table vazifasini bajaradi.
JNIEnv ayni threadga tegishli.
Uni boshqa threadga saqlab uzatish mumkin emas.
JavaVM
JavaVM* processdagi virtual machine’ni ifodalaydi.
U global saqlanishi mumkin.
Native thread JVM bilan ishlashi kerak bo‘lsa JavaVM orqali attach qilinadi va JNIEnv olinadi.
Thread tugaganda detach qilinadi.
Local reference
JNI method ichida olingan object reference odatda local reference hisoblanadi.
U native call tugaganda avtomatik bo‘shatiladi.
Loop ichida minglab local reference yaratilsa jadval to‘lishi mumkin.
DeleteLocalRef yoki local frame bilan erta tozalanadi.
Global reference
Object native calllar orasida saqlanishi kerak bo‘lsa global reference yaratiladi.
Garbage collector global reference mavjud objectni o‘chirmaydi.
Kerak bo‘lmaganda DeleteGlobalRef chaqirilmasa memory leak yuz beradi.
Weak global reference GC’ga objectni tozalash imkonini beradi.
Class topish
FindClass class nomi orqali reference qaytaradi.
Native method JVM tomonidan chaqirilgan threadda class loader contexti mos bo‘lishi mumkin.
Native yaratilgan thread’da application classini topish qiyinlashadi.
Kerakli class yoki class loader oldindan global reference sifatida saqlanishi mumkin.
Method ID
Method chaqirish uchun avval jmethodID olinadi.
Bunda:
aniq bo‘lishi kerak.
Signature xato bo‘lsa method topilmaydi va exception paydo bo‘lishi mumkin.
Method ID class lifecycle’iga bog‘liq.
Type signature
JNI primitive va object turlarni compact belgilar bilan ifodalaydi.
Masalan:
I— int;J— long;Z— boolean;V— void;Ljava/lang/String;— object;[— array.
Method signature parameterlar qavs ichida, keyin return type bilan yoziladi.
String
Java string UTF-16 representationga yaqin ishlaydi.
JNI UTF charlar uchun maxsus functionlar beradi.
“Modified UTF-8” oddiy standart UTF-8 bilan aynan bir xil emas.
Unicode data conversionida uzunlik, null byte va surrogate juftlar hisobga olinadi.
Array
Primitive array elementlariga:
- copy olish;
- critical section;
- region o‘qish;
- direct buffer
orqali murojaat qilish mumkin.
GetByteArrayElements har doim original memory pointerini qaytarishi kafolatlanmaydi.
Release mode o‘zgarishlarni qaytarish yoki bekor qilishni belgilaydi.
DirectByteBuffer
Native memory Java tomonda direct buffer sifatida ko‘rinishi mumkin.
Bu katta data nusxasini kamaytiradi.
Buffer capacity va native memory lifecycle’i aniq boshqariladi.
Java buffer mavjud paytda native memory bo‘shatilsa use-after-free yuz beradi.
Exception
JNI operation exception yaratsa native kod uni tekshirishi kerak.
Pending exception mavjud bo‘lsa ko‘p JNI functionlarni chaqirish mumkin emas.
Native kod:
ExceptionCheck;ExceptionDescribe;ExceptionClear;ThrowNew
kabi operationlardan foydalanadi.
Exceptionni sababsiz clear qilish xatoni yashiradi.
Native exception
C++ exception JVM boundary’dan tashqariga chiqmasligi kerak.
Boundary’da try-catch bilan ushlanadi.
Keyin Java exceptionga map qilinadi.
Aks holda process terminate bo‘lishi mumkin.
Thread attach
JVM yaratmagan native thread JNI’dan foydalanishdan oldin attach qilinadi.
Uzoq yashaydigan thread bir marta attach, tugaganda detach qilinadi.
Har chaqiriqda noto‘g‘ri attach/detach qilish overhead va leak yaratishi mumkin.
Androidda thread local destructorga oid usullar ishlatilishi mumkin.
Synchronization
JNI Java monitorini lock qilish imkonini beradi.
Native mutex va Java monitorini aralashtirish lock order muammosiga olib kelishi mumkin.
Deadlockdan qochish uchun boundary ichida uzun blocking operation bajarilmaydi.
Performance
JNI boundary callning o‘zi ma’lum overheadga ega.
Shu sababli:
- ko‘p mayda call o‘rniga batch;
- data copy’ni kamaytirish;
- method ID cache;
- class lookupni takrorlamaslik;
- primitive array
ishlatiladi.
Performance o‘lchov bilan tekshiriladi.
Obfuscation
Code shrinker Java class yoki method nomini o‘zgartirishi mumkin.
Native kod string orqali eski nomni qidirsagina runtime xato yuz beradi.
Keep rule yoki RegisterNatives uchun stable entry point ishlatiladi.
Generated binding obfuscation mapping bilan mos bo‘lishi kerak.
Security
JNI native kodga JVM himoyasidan tashqari memory access beradi.
Length, pointer, object type va thread holati tekshiriladi.
Untrusted inputni native parserga uzatishdan oldin format va limitlar belgilanadi.
Native kutubxona signing va supply-chain nazorati bilan olinadi.
Bog‘liq tushunchalar
Java Native Interface, JNI, JNIEnv, JavaVM, Native method, Local reference, Global reference, NDK, JVM, DirectByteBuffer, Native library