زیادہ تر JNI تنازعات پیچیدہ منطق سے پیدا نہیں ہوتے ہیں۔ یہ اعتراض کے حوالہ جات سے متعلق چھوٹی غلطیوں سے آتا ہے۔ یہ غلطیوں سے آتا ہے جیسے کسی حوالہ کے غلط ہونے کے بعد اسے برقرار رکھنا، اس کے جاری کیے جانے سے زیادہ تیزی سے حوالہ بنانا، یا کسی ایسے تھریڈ کا حوالہ دینا جس کے استعمال کی اجازت نہیں ہے۔
ان کیڑوں کو ٹریک کرنا مشکل ہے کیونکہ یہ اکثر اس لائن سے بہت دور ہوتے ہیں جس کی وجہ سے حادثہ ہوتا ہے، بعض اوقات کچھ منٹ بعد، کچرا جمع کرنے والے کے اندر۔
جاوا مقامی انٹرفیس C اور C++ کوڈ فراہم کرتا ہے جس میں JVM کو کال کرنے اور جاوا اشیاء کے ساتھ کام کرنے کا طریقہ ہے۔ مقامی کوڈ کو جاوا آبجیکٹ کے لیے خام اشارے نہیں ملتے ہیں۔ حوالہ حاصل کرنے کے بعد، ہر قسم کے حوالہ کے اپنے اصول ہوتے ہیں کہ یہ کب تک درست ہے، اسے کن دھاگوں پر استعمال کیا جا سکتا ہے، اور اسے جاری کرنے کا ذمہ دار کون ہے۔ ایک بار جب یہ اصول واضح ہو جاتے ہیں، تو زیادہ تر JNI تنازعات کو روکنا اور تشخیص کرنا آسان ہو جاتا ہے۔
یہ مضمون بتاتا ہے کہ مقامی، عالمی اور کمزور عالمی حوالہ جات کیسے کام کرتے ہیں، ہر ایک کے لیے ٹوٹے ہوئے یا تبدیل شدہ کوڈ کے ساتھ سب سے عام غلطیوں کو دیکھتا ہے، اور پیداوار تک پہنچنے سے پہلے ان مسائل کو پکڑنے کے لیے ٹولز اور پیٹرن (CheckJNI اور RAII ریپرز) کے ساتھ اختتام پذیر ہوتا ہے۔
مثالیں C++ اور Android کنونشنز کا استعمال کرتی ہیں، لیکن حوالہ کنونشن JNI تفصیلات سے ہیں اور تمام JVMs پر لاگو ہوتے ہیں۔
انڈیکس
شرطیں
آپ کو C++ اور Java کو پڑھنے میں ماہر ہونا چاہیے، اور JNI کے بنیادی فنکشنز لکھنا یا کم از کم پڑھنا چاہیے (فنکشنز کا اعلان کیا گیا ہے: extern "C" JNIEXPORT جاوا سے کال کی گئی۔ native طریقہ)۔
Android NDK سے واقفیت سے Android کے مخصوص حصوں میں مدد ملے گی، لیکن آپ کو بنیادی خیالات پر عمل کرنے کی ضرورت نہیں ہے۔
JNI حوالہ جات کیسے کام کرتے ہیں۔
جاوا آبجیکٹ ایک منظم ڈھیر پر رہتے ہیں اور جب بھی کوڑا اٹھانے والا میموری کو کمپیکٹ کرتا ہے تو اسے آزادانہ طور پر منتقل کیا جا سکتا ہے۔
اگر مقامی کوڈ اس آبجیکٹ کے لیے خام پوائنٹر رکھتا ہے، تو پوائنٹر اگلے کمپریشن کے بعد خود بخود باسی ہو جاتا ہے۔ JNI اس کے بجائے مقامی کوڈ میں ایک مبہم ہینڈل پاس کرکے اسے روکتا ہے۔ درج ذیل اقسام jobject, jclass, jstringاور jobjectArray تمام ہینڈل اس قسم کے ہیں۔
Native code JVM reference tables Managed heap
+-------------+ +----------------------+ +------------+
| jobject h + --------> | slot 17 : ptr + ------> | User obj |
+-------------+ +----------------------+ +------------+
(GC updates the ptr
when the object moves)
مندرجہ بالا خاکہ ان ڈائریکشن کو ظاہر کرتا ہے جو JNI حوالہ کو محفوظ بناتا ہے۔ مقامی کوڈ میں ہینڈل ہوتے ہیں (h)، جو JVM کی ملکیت میں تلاش کی میز میں ایک سلاٹ کی طرف اشارہ کرتا ہے۔ اس سلاٹ میں نظم شدہ ڈھیر پر آبجیکٹ کا جسمانی پتہ ہوتا ہے۔
جب کوڑا اٹھانے والا کسی چیز کو منتقل کرتا ہے، تو یہ سلاٹ میں محفوظ پوائنٹر کو اپ ڈیٹ کرتا ہے اور مقامی کوڈ میں موجود ہینڈل درست رہتا ہے۔ سلاٹ ایک GC جڑ کے طور پر بھی کام کرتا ہے۔ اس کا مطلب یہ ہے کہ جب تک سلاٹ موجود ہے، وہ جس چیز کی طرف اشارہ کرتا ہے اسے جمع نہیں کیا جا سکتا۔
حوالہ دینے کے بارے میں تمام اصول ایک سوال پر ابلتے ہیں: وہ سلاٹ کب جاری کیے جائیں گے؟
JNI میں تین قسم کے حوالہ جات ہیں، اور وہ بالکل اسی لحاظ سے مختلف ہیں: مقامی حوالہ جات میں سلاٹ خود بخود آزاد ہو جاتے ہیں جب مقامی طریقہ واپس آتا ہے۔ عالمی حوالہ جات میں سلاٹس کو اس وقت تک برقرار رکھا جاتا ہے جب تک کہ مقامی کوڈ انہیں واضح طور پر حذف نہ کر دے۔ کمزور عالمی حوالہ جات بھی حذف ہونے تک برقرار رہتے ہیں، لیکن وہ آبجیکٹ کو زندہ نہیں رکھتے، اس لیے ان کے پیچھے موجود شے کسی بھی وقت غائب ہو سکتی ہے۔
مقامی حوالہ جات
تقریباً تمام JNI فنکشنز جو جاوا آبجیکٹ کو لوٹاتے ہیں مقامی حوالہ جات واپس کرتے ہیں۔ FindClass, NewStringUTF, GetObjectArrayElement, CallObjectMethodاور GetObjectClass یہ سب کرتے ہیں۔ دلائل مقامی طریقوں پر منتقل ہوئے (بشمول thiz یا jclass (جامد طریقوں کے لیے) بھی ایک مقامی حوالہ ہے۔
ایک مقامی حوالہ صرف مقامی طریقہ کال کے اندر درست ہے جس نے اسے بنایا ہے، اور صرف اس تھریڈ پر جس نے اسے بنایا ہے۔ جب مرکزی طریقہ جاوا پر واپس آجاتا ہے، JVM اس کال کے دوران بنائے گئے تمام مقامی حوالہ جات کو ایک قدم میں جاری کرتا ہے۔ یہ مختصر افعال کے لیے آسان ہے اور یہی وجہ ہے کہ زیادہ تر JNI کوڈ انہیں کبھی کال نہیں کرتا ہے۔ DeleteLocalRef کوئی بھی
سہولت کی اپنی حد ہوتی ہے۔ مقامی تلاش کی میز فی کال فریم ایک مقررہ صلاحیت ہے. JNI تفصیلات صرف 16 مقامی حوالوں کے لیے جگہ کی ضمانت دیتی ہے، لہذا آپ کو کال کرنے کی ضرورت ہے: EnsureLocalCapacity اگر آپ کو مزید ضرورت ہے۔
درحقیقت، JVM اس سے کہیں زیادہ فراہم کرتا ہے۔ پچھلے اینڈرائیڈ ورژنز میں، ٹیبلز کو 512 اندراجات تک محدود رکھا گیا تھا، اس سے زیادہ ہونے سے عمل کریش ہو جائے گا۔ local reference table overflow (max=512). اگرچہ ART کے نئے ورژنز میں حد کو نمایاں طور پر بڑھا دیا گیا ہے، لیکن کوئی بھی کوڈ جو لامحدود لوپ میں مقامی حوالہ جات تخلیق کرتا ہے اس پر عمل درآمد نہیں کیا جائے گا۔
نقصان 1: مقامی حوالوں کو لوپس میں لیک کرنا
سب سے عام مقامی حوالہ کیڑے ایسے لوپس ہیں جو ہر تکرار میں ایک نیا حوالہ بناتے ہیں اور اسے کبھی جاری نہیں کرتے ہیں۔
extern "C" JNIEXPORT void JNICALL
Java_com_example_NameProcessor_processNames(JNIEnv* env, jobject thiz,
jobjectArray names) {
jsize count = env->GetArrayLength(names);
for (jsize i = 0; i < count; i++) {
jstring name = static_cast(env->GetObjectArrayElement(names, i));
const char* utf = env->GetStringUTFChars(name, nullptr);
if (utf == nullptr) return;
LogName(utf);
env->ReleaseStringUTFChars(name, utf);
}
}
یہ فنکشن جاوا میں دہرایا جاتا ہے۔ String[] ہر آئٹم کو ریکارڈ کریں۔ ہر کال ہے۔ GetObjectArrayElement ایک نیا مقامی حوالہ بناتا ہے اور اسے ٹیبل میں محفوظ کرتا ہے۔
آپ کا کوڈ UTF-8 بفر کو صحیح طریقے سے آزاد کرتا ہے۔ ReleaseStringUTFCharsتاہم، یہ صرف کریکٹر ڈیٹا جاری کرتا ہے۔ یہ کچھ نہیں کرتا jstring حوالہ خود۔
10 نام ٹھیک ہیں۔ اگر 100,000 نام ہیں تو ٹیبل بھر جائے گا اور عمل رک جائے گا۔ چونکہ یونٹ ٹیسٹ عام طور پر چھوٹی صفوں کو پاس کرتے ہیں، اس لیے کیڑے کو چھوٹنا آسان ہے۔
ایک سیدھا حل یہ ہے کہ ہر اس حوالہ کو حذف کر دیا جائے جس کی اب ضرورت نہیں ہے۔
for (jsize i = 0; i < count; i++) {
jstring name = static_cast(env->GetObjectArrayElement(names, i));
const char* utf = env->GetStringUTFChars(name, nullptr);
if (utf == nullptr) {
env->DeleteLocalRef(name);
return;
}
LogName(utf);
env->ReleaseStringUTFChars(name, utf);
env->DeleteLocalRef(name);
}
اب لوپ کہا جاتا ہے. DeleteLocalRef ہر تکرار کے اختتام پر اور ابتدائی واپسی کے راستے پر، ٹیبل ایک وقت میں اس لوپ کا ایک سے زیادہ حوالہ کبھی نہیں رکھے گا۔
ابتدائی واپسی اہم ہے۔ GetStringUTFChars رپورٹ nullptr اگر میموری ایلوکیشن ناکام ہو جاتی ہے تو، میموری ایلوکیشن بھی پیچھے رہ جاتی ہے۔ OutOfMemoryError چونکہ یہ زیر التواء ہے، درست جواب یہ ہے کہ فوری طور پر جاوا پر واپس آجائیں۔
ایک بار جب ایک سے زیادہ حوالہ جات (کلاسز، طریقہ کار کے نتائج، تار) تکرار کے ذریعے بنائے جاتے ہیں، تو ہر حوالہ کو دستی طور پر حذف کرنا مشکل اور غلطی کا شکار ہوتا ہے۔ PushLocalFrame اور PopLocalFrame کیس کو ہینڈل کریں۔
for (jsize i = 0; i < count; i++) {
if (env->PushLocalFrame(8) != 0) return;
jobject user = env->GetObjectArrayElement(users, i);
jstring name = static_cast(env->CallObjectMethod(user, gGetName));
jstring email = static_cast(env->CallObjectMethod(user, gGetEmail));
SaveUser(env, name, email);
env->PopLocalFrame(nullptr);
}
PushLocalFrame(8) کم از کم آٹھ حوالوں کے لیے جگہ کے ساتھ مقامی ریفرنس ٹیبل میں ایک نیا دائرہ کھولیں۔ اس نقطہ کے بعد بنائے گئے کوئی بھی مقامی حوالہ جات نئے فریم سے تعلق رکھتے ہیں۔ PopLocalFrame(nullptr) ان سب کو ایک ساتھ چھوڑ دو user, nameاور email وہ ہر تکرار کے اختتام پر ایک ساتھ جاری کیے جاتے ہیں۔
اگر PushLocalFrame اگر آپ ایک غیر صفر قدر واپس کرتے ہیں، تو JVM ایک فریم مختص نہیں کر سکتا اور OutOfMemoryError چونکہ یہ زیر التواء ہے، فنکشن فوراً واپس آجاتا ہے۔
اگر آپ کو فریم رکھنے کے لیے ایک حوالہ درکار ہے تو اس حوالہ کو پاس کریں۔ PopLocalFrame اس کے بجائے nullptrفنکشن بیرونی فریم میں اس آبجیکٹ کا ایک نیا مقامی حوالہ واپس کرتا ہے۔
نقصان 2: جامد متغیر میں مقامی حوالوں کو کیش کرنا
چونکہ کلاس اور طریقہ IDs تلاش کرنا نسبتاً سست ہے، مقامی لائبریریاں اکثر انہیں کیش کرتی ہیں۔ غلطی کلاس کو مقامی حوالہ کے طور پر کیش کر رہی ہے۔
static jclass gUserClass;
static jmethodID gGetName;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = nullptr;
if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
gUserClass = env->FindClass("com/example/User"); // Bug: local reference
gGetName = env->GetMethodID(gUserClass, "getName", "()Ljava/lang/String;");
return JNI_VERSION_1_6;
}
JNI_OnLoad لائبریری لوڈ ہونے پر ایک بار چلتا ہے، یہ ورژن نتائج کو محفوظ کرتا ہے: FindClass بعد میں استعمال کے لیے اسے ایک جامد متغیر میں رکھیں۔ مسئلہ یہ ہے۔ FindClass یہ ایک مقامی حوالہ واپس کرتا ہے اور اس حوالہ کو اس طرح آزاد کیا جاتا ہے: JNI_OnLoad رپورٹ ہر بار جب آپ اسے بعد میں استعمال کریں۔ gUserClass یہ ان سلاٹوں سے گزرتا ہے جو شاید پہلے ہی آزاد ہو چکے ہوں اور کسی بالکل مختلف شے کے لیے دوبارہ استعمال کیے جائیں۔
مایوس کن بات یہ ہے کہ یہ کوڈ اکثر کام کرتا دکھائی دیتا ہے۔ کچھ JVMs میں، ایسا ہوتا ہے کہ پرانے ہینڈل اب بھی کچھ وقت کے لیے صحیح کلاس کی طرف اشارہ کرتے ہیں۔ چیک جے این آئی کے ساتھ ART میں، مندرجہ ذیل خرابی کے ساتھ عمل ختم ہوجاتا ہے: accessed stale local reference. چیک جے این آئی کے بغیر، آپ کو بے ترتیب کریش یا غلط اشیاء کو کال کرنا پڑے گا۔
ایک حل یہ ہے کہ کلاس کو محفوظ کرنے سے پہلے اسے عالمی حوالے سے فروغ دیا جائے۔
jclass localClass = env->FindClass("com/example/User");
if (localClass == nullptr) return JNI_ERR;
gUserClass = static_cast(env->NewGlobalRef(localClass));
env->DeleteLocalRef(localClass);
if (gUserClass == nullptr) return JNI_ERR;
gGetName = env->GetMethodID(gUserClass, "getName", "()Ljava/lang/String;");
if (gGetName == nullptr) return JNI_ERR;
NewGlobalRef اسی کلاس کا دوسرا حوالہ بناتا ہے۔ JNI_OnLoad رپورٹ اصل مقامی حوالہ کی مزید ضرورت نہیں ہے، لہذا کوڈ اسے فوری طور پر حذف کر دیتا ہے۔ تمام کالز تصدیق شدہ ہیں۔ nullptrکیونکہ FindClass اور GetMethodID واپسی nullptr اگر کلاس یا طریقہ موجود نہیں ہے، تو زیر التواء رعایت کو تنہا چھوڑ دیں (مثلاً کسی مبہم ٹول میں نام تبدیل کرنے کے بعد)۔
کہ jmethodID انہیں ایک ہی علاج کی ضرورت نہیں ہے۔ طریقہ IDs اور فیلڈ IDs آبجیکٹ حوالہ جات نہیں ہیں اور اس وجہ سے حوالہ جدول میں ٹریک نہیں کیا جاتا ہے۔ یہ اس وقت تک درست ہے جب تک کہ اس کا تعلق جس کلاس سے ہے اسے لوڈ کیا جائے۔ کسی کلاس کا عالمی حوالہ رکھنا اس بات کی ضمانت دیتا ہے کہ اسے کبھی نہیں اتارا جائے گا۔ یہ عالمی سطح پر کلاسز کو ان کی شناخت کے ساتھ کیش کرنے کی ایک اور وجہ ہے۔
عالمی حوالہ
عالمی حوالہ جات واضح طور پر استعمال کرتے ہوئے بنائے گئے ہیں: NewGlobalRef یہ اس وقت تک اثر میں رہتا ہے جب تک کہ مقامی کوڈ نہیں کہا جاتا۔ DeleteGlobalRef. یہ کسی بھی دھاگے سے دستیاب ہے اور حوالہ شدہ شے کو پوری زندگی زندہ رکھتا ہے۔ یہ اسے مقامی کوڈ کے لیے ایک اچھا ٹول بناتا ہے جسے کالوں میں برقرار رہنے کی ضرورت ہوتی ہے، جیسے کیشڈ کلاسز، کال بیک آبجیکٹ، یا جاوا آبجیکٹ جو مقامی وسائل کے مالک ہیں۔
لاگت یہ ہے کہ جے وی ایم اپنے طور پر عالمی حوالہ جات جاری نہیں کرتا ہے۔ کوڑا اٹھانے والا اسے جڑ کے طور پر سمجھتا ہے، اس لیے جس چیز اور اس کے ساتھ جو کچھ بھی منسلک ہو سکتا ہے وہ اس وقت تک میموری میں رہتا ہے جب تک کہ حوالہ جات کو حذف نہ کر دیا جائے۔
عالمی حوالہ جات بھی مقررہ سائز کے جدولوں میں رہتے ہیں۔ اینڈرائیڈ پر، یہ حد 51,200 آئٹمز ہے، جس سے زیادہ عمل ختم ہوجاتا ہے۔ global reference table overflow.
نقصان 3: عالمی حوالہ رساو
عالمی حوالہ جات کا لیک عام طور پر کوڈ میں ہوتا ہے جو ہر بار جب کوئی شے پرانی کو آزاد کیے بغیر رجسٹرڈ ہوتا ہے تو ایک نیا عالمی حوالہ تخلیق کرتا ہے۔
static jobject gListener;
extern "C" JNIEXPORT void JNICALL
Java_com_example_Sensor_setListener(JNIEnv* env, jobject thiz, jobject listener) {
gListener = env->NewGlobalRef(listener);
}
تمام کرنسیوں setListener ایک نیا عالمی حوالہ بناتا ہے اور اگلے ذخیرہ شدہ پچھلے ہینڈل کو اوور رائٹ کرتا ہے۔ gListener. پرانا حوالہ ابھی بھی جدول میں ہے، لیکن اسے حذف نہیں کیا جا سکتا کیونکہ اب اس کی طرف اشارہ کرنے والی کوئی چیز نہیں ہے۔ ہر لیک شدہ حوالہ سامعین کی چیز کو بھی پن کرتا ہے، اور سننے والے اکثر اندرونی طبقے ہوتے ہیں جو پوری سرگرمی یا ٹکڑے کا حوالہ رکھتے ہیں۔ نتیجے کے طور پر، جب بھی اسکرین گھومتی ہے تو میموری کا لیک بڑھ جاتا ہے اور آخر کار کریش اس وقت ہوتا ہے جب ٹیبل اوور فلو ہوجاتا ہے۔
ترمیم شدہ ورژن پرانے حوالہ کو جاری کرتا ہے اور جاوا کو سننے والوں کو صاف کرنے کا ایک طریقہ فراہم کرتا ہے۔
static std::mutex gListenerMutex;
static jobject gListener = nullptr;
extern "C" JNIEXPORT void JNICALL
Java_com_example_Sensor_setListener(JNIEnv* env, jobject thiz, jobject listener) {
std::lock_guard<:mutex> lock(gListenerMutex);
if (gListener != nullptr) {
env->DeleteGlobalRef(gListener);
gListener = nullptr;
}
if (listener != nullptr) {
gListener = env->NewGlobalRef(listener);
}
}
یہ ورژن نئے عالمی حوالہ جات بنانے سے پہلے موجودہ عالمی حوالہ جات کو حذف کر دیتا ہے، اس لیے کسی بھی وقت زیادہ سے زیادہ ایک سننے والا حوالہ موجود ہوتا ہے۔ گزرنا null یہ جاوا میں سننے والے کو مکمل طور پر صاف کرنے اور اسے جاوا کی طرف چھوڑنے کا ایک صاف طریقہ فراہم کرتا ہے۔ onDestroy یا close(). Mutex حفاظت کرتا ہے۔ gListener اس کی وجہ یہ ہے کہ سننے والے کو مین ورکر تھریڈ سے پڑھا جا سکتا ہے جبکہ جاوا اسے مین تھریڈ سے اوور رائیڈ کر دیتا ہے۔ تالے کے بغیر، کارکن ہینڈلز کو پڑھ سکتے ہیں جو کچھ لمحے پہلے حذف کر دیے گئے تھے۔
ایک اچھی عادت ہر بار ان کو جوڑنا ہے۔ NewGlobalRef جب مماثل مقام واضح طور پر پہچانا جاتا ہے۔ DeleteGlobalRef ایسا ہوتا ہے۔ اگر آپ اس مقام کی طرف اشارہ نہیں کر سکتے تو آپ کا حوالہ لیک ہو سکتا ہے۔
کمزور عالمی حوالہ جات
کے ساتھ ایک کمزور عالمی حوالہ تیار کیا جاتا ہے۔ NewWeakGlobalRefیہ ایک عالمی حوالہ کی طرح کام کرتا ہے کہ یہ کالوں میں برقرار رہتا ہے اور تمام تھریڈز کے لیے دستیاب ہے۔ فرق یہ ہے کہ یہ اعتراض کو زندہ نہیں رکھتا۔ جاوا میں، اگر کوئی اور چیز کسی چیز کا حوالہ نہیں دیتی ہے، تو کچرا جمع کرنے والا اسے جمع کرسکتا ہے، اور ایک کمزور حوالہ سے مراد: null.
کمزور حوالہ جات اس وقت کارآمد ہوتے ہیں جب مقامی کوڈ جاوا آبجیکٹ کو اس آبجیکٹ کی زندگی پر کنٹرول کیے بغیر واپس کال کرنا چاہتا ہے۔ ایک اہم مثال مقامی آڈیو انجن ہے جو UI اجزاء کو مطلع کرتا ہے۔ صارف کے اسکرین چھوڑنے کے بعد انجن کو UI کو فعال نہیں رکھنا چاہیے، لیکن انجن کو UI تک رسائی کے قابل ہونا چاہیے جب تک یہ موجود ہو۔
نقصان 4: کمزور عالمی حوالوں کو براہ راست استعمال کرنا
کمزور حوالوں کے ساتھ غلطی یہ ہے کہ ان کا استعمال اس طرح کیا جائے جیسے وہ مضبوط حوالہ جات ہوں۔
static jweak gCallback;
void NotifyProgress(JNIEnv* env, jint percent) {
if (!env->IsSameObject(gCallback, nullptr)) {
env->CallVoidMethod(gCallback, gOnProgress, percent); // Bug: race with GC
}
}
کوڈ چیک کرتا ہے کہ آیا اس کے پیچھے موجود چیز موجود ہے۔ gCallback یہ جمع کیا جاتا ہے، اور اگر یہ جمع نہیں کیا جاتا ہے، تو ہم اس پر ایک طریقہ کہتے ہیں. تصدیق اور درخواست دو الگ الگ مراحل ہیں، اور کوڑا اٹھانے والا ان کے درمیان چل سکتا ہے۔ پھر CallVoidMethod آپ کو ایک ایسی چیز کا حوالہ ملتا ہے جو اب موجود نہیں ہے۔ یہ وقت کے استعمال کے مقابلے کے لیے نصابی کتاب کی جانچ پڑتال کا وقت ہے اور یہ صرف میموری کے دباؤ میں نظر آتا ہے۔
ایک محفوظ نقطہ نظر یہ ہے کہ پہلے کسی مقامی حوالہ کے کمزور حوالہ کو فروغ دیا جائے۔
void NotifyProgress(JNIEnv* env, jint percent) {
jobject callback = env->NewLocalRef(gCallback);
if (callback == nullptr) return;
env->CallVoidMethod(callback, gOnProgress, percent);
env->DeleteLocalRef(callback);
}
NewLocalRef دو میں سے ایک واپسی nullptr (آبجیکٹ کو جمع کیا گیا ہے) یا ایک مضبوط مقامی حوالہ واپس کرتا ہے۔ اگر آپ کے کوڈ میں وہ مقامی حوالہ موجود ہے تو، کوڑا اٹھانے والا اس شے کو اس وقت تک جمع نہیں کر سکتا جب تک کہ حوالہ ختم نہیں ہو جاتا، جس سے کال محفوظ ہو جاتی ہے۔
NewLocalRef اسے JNI 1.2 میں شامل کیا گیا تھا اور یہ تمام حالیہ JVMs اور تمام Android ورژنز پر دستیاب ہے۔ جب خود کمزور حوالہ کی ضرورت نہ رہے تو اسے استعمال کرکے آزاد کریں: DeleteWeakGlobalRefنہیں DeleteGlobalRef.
نقصان 5: JNIEnv کو تھریڈز میں شیئر کرنا
کہ JNIEnv* تمام بنیادی طریقوں کو پاس کیے گئے پوائنٹرز موجودہ دھاگے کے ساتھ منسلک ہیں۔ فی تھریڈ حالت کی طرف پوائنٹس، جیسے مقامی تلاش کی میزیں اور زیر التواء مستثنیات۔ اگر آپ اسے عالمی سطح پر اسٹور کرتے ہیں اور اسے کسی اور تھریڈ سے استعمال کرتے ہیں، تو وہ حالت خراب ہو جائے گی۔
static JNIEnv* gEnv; // Bug: JNIEnv is per thread
extern "C" JNIEXPORT void JNICALL
Java_com_example_Downloader_start(JNIEnv* env, jobject thiz) {
gEnv = env;
std::thread([] {
DownloadFile();
gEnv->CallStaticVoidMethod(gDownloaderClass, gOnComplete);
}).detach();
}
پہلے سے طے شدہ طریقہ اس کے مواد کو محفوظ کرتا ہے۔ JNIEnv* اس کے بعد ایک ورکر تھریڈ شروع ہوتا ہے جو اسے ڈاؤن لوڈ مکمل ہونے کے بعد استعمال کرتا ہے۔ اس وقت ذخیرہ شدہ پوائنٹر دوسرے دھاگے سے تعلق رکھتا ہے (جسے تھریڈ کہا جاتا ہے)۔ start)، وہ تھریڈ ایک ہی وقت میں دوسرے JNI کوڈ پر عمل درآمد کر رہا ہے۔ CheckJNI کا استعمال کرتے ہوئے ART مندرجہ ذیل پیغام کے ساتھ فوری طور پر ناکام ہو جاتا ہے: JNIEnv غلط تھریڈ پر استعمال کیا گیا۔ چیک جے این آئی کے بغیر، غیر متوقع کرپشن ہو گی۔
صحیح نمونہ ہے۔ JavaVM*یہ تمام تھریڈز کے ذریعے شیئر کیا جاتا ہے، اور ہر تھریڈ خود سے جڑتا ہے اور اپنا تھریڈ حاصل کرتا ہے۔ JNIEnv*.
static JavaVM* gVm;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
gVm = vm;
// Cache classes and method IDs here.
return JNI_VERSION_1_6;
}
void WorkerThread() {
JNIEnv* env = nullptr;
if (gVm->AttachCurrentThread(&env, nullptr) != JNI_OK) return;
DownloadFile();
env->CallStaticVoidMethod(gDownloaderClass, gOnComplete);
if (env->ExceptionCheck()) env->ExceptionClear();
gVm->DetachCurrentThread();
}
JNI_OnLoad محفوظ کریں JavaVM*یہ عمل کی زندگی کے لیے درست ہے۔ ورکر تھریڈ کال AttachCurrentThread اپنے آپ کو JVM اور کے ساتھ رجسٹر کریں۔ JNIEnv* پھر اسے خود ہی بلا لیں۔ DetachCurrentThread اس کے ختم ہونے سے پہلے. Android NDK اعلان کرتا ہے: AttachCurrentThread لے کر JNIEnv**ڈیسک ٹاپ JDK ہیڈر ہے۔ void**تو آپ کا ڈیسک ٹاپ کوڈ ہوگا۔ reinterpret_cast.
علیحدگی کوئی آپشن نہیں ہے۔ چونکہ کلین اپ کو ٹرگر کرنے کا کوئی بنیادی طریقہ نہیں ہے، اس لیے متعلقہ مین تھریڈ پر بنائے گئے مقامی حوالہ جات خود بخود آزاد نہیں ہوتے ہیں۔ یہ صرف منقطع ہونے پر جاری کیا جاتا ہے۔
اینڈرائیڈ پر، اے آر ٹی اس عمل کو روک دیتا ہے کیونکہ جڑے ہوئے مرکزی دھاگے کے باہر نکل جاتے ہیں۔ اگر کوئی دھاگہ اپنی پوری زندگی کے لیے جڑا رہے گا، تو ایک عام طریقہ یہ ہے کہ تھریڈ کو رجسٹر کیا جائے۔ pthread_key_create کالنگ تباہ کن DetachCurrentThread ایک بار جب تھریڈ نکل جاتا ہے، کوئی کوڈ پاتھ اس کے بارے میں نہیں بھول سکتا۔
بھی gDownloaderClass یہ ایک عالمی حوالہ ہونا چاہیے ان وجوہات کی بناء پر جو خرابی 2 میں شامل ہیں۔ مقامی حوالہ جات اس سے آگے کے دھاگوں کو عبور نہیں کرسکتے JNIEnv* آپ یہ کر سکتے ہیں۔
پٹفال 6: مقامی دھاگے سے FindClass کو کال کرنا
ایک متعلقہ حیرت اس وقت ظاہر ہوتی ہے جب نئے جڑے ہوئے مقامی دھاگے کو طلب کیا جاتا ہے۔ FindClass ایپ کی اپنی کلاسوں میں سے ایک کے لیے:
void WorkerThread() {
JNIEnv* env = nullptr;
gVm->AttachCurrentThread(&env, nullptr);
jclass cls = env->FindClass("com/example/Downloader"); // Returns nullptr
// ...
gVm->DetachCurrentThread();
}
جب FindClass جب مقامی طریقہ کے اندر بلایا جاتا ہے، تو JVM جاوا کلاس کے کلاس لوڈر کا استعمال کرتا ہے جس نے طریقہ کار کا اعلان کیا ہے، لہذا ایپلیکیشن کلاس کو صحیح طریقے سے حل کیا جاتا ہے۔
دھاگوں کو مقامی کوڈ کے ذریعے بنایا اور منسلک کیا گیا۔ AttachCurrentThread اسٹیک پر کوئی جاوا فریم نہیں ہیں۔ اس صورت میں، JVM سسٹم کلاس لوڈر پر واپس آتا ہے، جو صرف فریم ورک کلاسز کے بارے میں جانتا ہے۔ استفسار ناکام ہوجاتا ہے۔ FindClass رپورٹ nullptrاور ClassNotFoundException ہولڈ پر کوڈ جو واپسی کی قیمت کو چیک نہیں کرتا ہے ان کالوں پر کریش ہو جائے گا جو استعمال کرتی ہیں: cls.
معیاری حل یہ ہے کہ آپ کو درکار تمام کلاسز کو تلاش کریں۔ JNI_OnLoad (آپ کی ایپلی کیشن کے کلاس لوڈر کے ساتھ چلتا ہے)، ہر ایک کو عالمی حوالہ کے طور پر اسٹور کریں اور انہیں کبھی کال نہ کریں۔ FindClass دیسی دھاگے میں۔ اگر آپ کو کلاسوں کو متحرک طور پر حل کرنے کی ضرورت ہے تو، اپنی درخواست کے لیے عالمی حوالہ جات کیش کریں۔ ClassLoader اعتراض کرتے وقت JNI_OnLoad اور مجھے بلاؤ loadClass اس کے بجائے ورکر تھریڈ کے طریقے استعمال کریں۔
نقصان 7: زیر التواء مستثنیات کو نظر انداز کرنا
اگر JNI کال ناکام ہوجاتی ہے یا مقامی کوڈ سے کال کردہ جاوا کوڈ مستثنیٰ ہے، تو یہ مقامی اسٹیک کو آزاد نہیں کرے گا۔ اسے زیر التواء کے طور پر لاگ کیا جائے گا اور پہلے سے طے شدہ فنکشن چلتا رہے گا۔ زیادہ تر JNI فنکشنز کو کال کرنا جاری رکھنا جب کہ ایک استثناء زیر التواء ہے غیر متعینہ سلوک ہے۔
jstring name = static_cast(env->CallObjectMethod(user, gGetName));
const char* utf = env->GetStringUTFChars(name, nullptr); // Bug if getName threw
اگر getName() پھینکنا، CallObjectMethod رپورٹ nullptr استثنیٰ کو زیر التوا چھوڑ دیں۔ اگلی لائن اس سے گزرتی ہے۔ nullptr ~ میں GetStringUTFChars ایک استثناء جو ایک ساتھ دو اصولوں کو توڑتا ہے ابھی تک زیر التواء ہے۔ CheckJNI استعمال کرنے سے ART اس طرح کے پیغام کے ساتھ کریش ہو جاتا ہے: JNI GetStringUTFChars called with pending exception. اس کے بغیر، عمل عام طور پر null dereference کے ساتھ کریش ہو جائے گا۔
jstring name = static_cast(env->CallObjectMethod(user, gGetName));
if (env->ExceptionCheck()) {
return nullptr;
}
const char* utf = env->GetStringUTFChars(name, nullptr);
if (utf == nullptr) {
return nullptr;
}
ہر ممکنہ کال کے بعد فکسڈ کوڈ چیک کرتا ہے۔ ExceptionCheck() اگر کوئی استثناء زیر التواء ہے، تو یہ فوراً واپس آجاتا ہے۔ زیر التواء استثناء کے ساتھ مقامی طریقہ سے واپسی کی اجازت اور مفید ہے۔ جیسے ہی کنٹرول واپس آتا ہے، جاوا دوبارہ استثنیٰ پھینک دیتا ہے، لہذا جاوا کال کرنے والا اسے باقاعدہ استثناء کے طور پر دیکھتا ہے۔ native طریقہ
اگر آپ کا آبائی کوڈ اس کی بجائے ناکامی کو خود ہینڈل کرنا چاہتا ہے، تو یہ کال کرے گا: ExceptionClear() مزید JNI کال کرنے سے پہلے، فنکشنز کا ایک چھوٹا سیٹ جیسے: DeleteLocalRef, DeleteGlobalRefاور Release ایک استثناء زیر التواء ہونے کے دوران فنکشن کو واضح طور پر اجازت دی جاتی ہے، لہذا یہ واپس آنے سے پہلے صفائی کرنا جاری رکھ سکتا ہے۔
نقصان 8: مساوی آپریٹر کا استعمال کرتے ہوئے حوالہ جات کا موازنہ کرنا
چونکہ حوالہ ایک ہینڈل ہے نہ کہ آبجیکٹ ایڈریس، اس لیے دو مختلف ہینڈلز ایک ہی جاوا آبجیکٹ کا حوالہ دے سکتے ہیں۔
if (listener == gListener) { // Bug: compares handles, not objects
RemoveListener();
}
اس مقابلے میں، listener موجودہ کال کا پاس کردہ ایک مقامی حوالہ ہے۔ gListener پہلے سے تخلیق کردہ عالمی حوالہ۔ اگرچہ دونوں ایک ہی جاوا آبجیکٹ کا حوالہ دیتے ہیں، وہ مختلف ٹیبلز میں مختلف سلاٹس پر قبضہ کرتے ہیں، اس لیے ان کے ہینڈل کی قدریں مختلف ہیں، اس لیے موازنہ غلط ہے۔ وصول کنندہ کو ہٹایا نہیں جاتا ہے۔
if (env->IsSameObject(listener, gListener)) {
RemoveListener();
}
IsSameObject جے وی ایم سے پوچھتا ہے کہ کیا دونوں ہینڈل ایک ہی آبجیکٹ (جاوا آبجیکٹ) کو حل کرتے ہیں۔ == سیمنٹکس جو آپ اصل میں چاہتے ہیں۔ گزرنا nullptr دوسری دلیل یہ چیک کرنے کا معیاری طریقہ بھی ہے کہ آیا کمزور عالمی حوالہ صاف ہو گیا ہے۔ جیسا کہ Pitfall 4 میں دیکھا گیا ہے، NewLocalRef جب آپ بعد میں آئٹم کو استعمال کرنے کی کوشش کرتے ہیں تو یہ زیادہ محفوظ ہوتا ہے۔
چیک جے این آئی کے ساتھ حوالہ کیڑے کیسے پکڑیں۔
اس مضمون میں زیادہ تر کیڑے یا تو حادثاتی طور پر متحرک ہو جاتے ہیں یا اصل غلطی سے غیر متعلق جگہوں پر کریش ہو جاتے ہیں۔ CheckJNI ایک توسیعی توثیق موڈ ہے جس کی وجہ سے یہ غلط کال کی نشاندہی کرنے والے پیغام کے ساتھ زور سے اور فوری طور پر ناکام ہو جائے گا۔
اینڈرائیڈ پر، چیک جے این آئی خود بخود ان ایپس کے لیے فعال ہو جاتا ہے جن کے ساتھ بنایا گیا ہے: android:debuggable="true" اور ایمولیٹر پر۔ اپنی حسب ضرورت تعمیر کے ساتھ فزیکل ڈیوائس پر تمام ایپس کے لیے اس کو زبردستی کرنے کے لیے، درج ذیل کمانڈ کو چلائیں اور پھر ایپ کو دوبارہ شروع کریں۔
adb shell setprop debug.checkjni 1
یہ ایک سسٹم پراپرٹی سیٹ کرتا ہے جو ART کو مستقبل میں شروع ہونے والے تمام ایپ پروسیسز کے لیے CheckJNI کو فعال کرنے کی ہدایت کرتا ہے۔ یہ پہلے سے چل رہے عمل کو متاثر نہیں کرتا ہے، لہذا آپ کو ایپ کو دوبارہ شروع کرنے کی ضرورت ہوگی۔
اگر آپ اسے فعال کرتے ہیں، تو ART تمام JNI کالوں کی توثیق کرے گا۔ باسی مقامی حوالہ جات، غلط دھاگے سے استعمال شدہ حوالہ جات، زیر التواء استثناء کے ساتھ کالز، JNIEnv* غلط دھاگے میں استعمال ہونے والا پوائنٹر، مماثل نہیں۔ Release کال اور غلط طریقہ ID۔ اگر چیک ناکام ہوجاتا ہے، تو ART پرنٹ کیا جاتا ہے۔ JNI DETECTED ERROR IN APPLICATION اس کو لاگ کیٹ میں مسئلے کی تفصیل اور بنیادی اسٹیک ٹریس کے ساتھ لاگ کیا جاتا ہے، اور پھر اسقاط ہوجاتا ہے۔
ڈیسک ٹاپ JVM پر آپ پاس کریں گے: -Xcheck:jni اس کے بجائے براہ کرم اسے جھنڈا لگائیں۔
java -Xcheck:jni -Djava.library.path=build/libs -jar app.jar
یہ ایپلیکیشن کو ہاٹ اسپاٹ کے JNI معائنہ کے ساتھ چلائے گا اور مقامی لائبریریوں کو اس سے لوڈ کرے گا: build/libs. HotSpot اسی طرح کے بہت سے مسائل کے لیے انتباہات پرنٹ کرتا ہے، جیسے زیر التواء مستثنیات یا غلط حوالوں کے ساتھ کی گئی کالز۔ چیکنگ عام طور پر ART کے مقابلے میں کم سخت ہوتی ہے، جس کے نتیجے میں ایسی ایپس ہوتی ہیں جو ماحول میں صاف ستھرا چلتی ہیں: -Xcheck:jni اینڈرائیڈ پر جے این آئی کی جانچ پڑتال اب بھی ڈیسک ٹاپ پر ناکام ہو سکتی ہے۔ دونوں پلیٹ فارمز پر فعال کردہ CheckJNI کے ساتھ ٹیسٹ چلانے، بشمول وہ ٹیسٹ جو بڑی صفوں کو پاس کرتے ہیں اور مقامی دھاگوں پر آپریشنز چلاتے ہیں، آپ کو زیادہ تر حوالہ جات کی خرابیوں کو ان کے جاری ہونے سے بہت پہلے پکڑنے میں مدد ملے گی۔
RAII کے ساتھ حوالہ جات کا محفوظ طریقے سے انتظام کیسے کریں۔
غیر فعالی DeleteLocalRef اور DeleteGlobalRef کال کرنا بھولنا آسان ہے۔ یہ خاص طور پر ابتدائی واپسی کے راستے پر درست ہے۔ C++ میں، آپ کسی دائرہ کار کے حوالے کی زندگی کو اسی طرح منسلک کر سکتے ہیں۔ std::unique_ptr ہیپ میموری کا انتظام کرتا ہے۔
template
class ScopedLocalRef {
public:
ScopedLocalRef(JNIEnv* env, T ref) : env_(env), ref_(ref) {}
~ScopedLocalRef() {
if (ref_ != nullptr) env_->DeleteLocalRef(ref_);
}
ScopedLocalRef(const ScopedLocalRef&) = delete;
ScopedLocalRef& operator=(const ScopedLocalRef&) = delete;
T get() const { return ref_; }
private:
JNIEnv* env_;
T ref_;
};
ScopedLocalRef یہ مقامی حوالہ کی ملکیت لیتا ہے اور اسے ڈسٹرکٹر میں حذف کر دیتا ہے، اس لیے اس حوالہ کو آزاد کیا جاتا ہے (عام تکمیل، جلد تکمیل) اس سے قطع نظر کہ بند کرنے کا دائرہ کیسے ختم ہوتا ہے۔ returnیا C++ مستثنیات)۔ کاپی کنسٹرکٹر اور کاپی اسائنمنٹ کو حذف کردیا گیا ہے کیونکہ ایک ہی ہینڈل والے دونوں ریپرز اسے حذف کرنے کی کوشش کرتے ہیں۔ کہ get() طریقہ JNI فنکشن کو پاس کرنے کے لیے ایک خام ہینڈل لوٹاتا ہے۔
اس ریپر کا استعمال کرتے ہوئے، پٹفال 1 کا لوپ چھوٹا ہے اور لیک نہیں ہو سکتا۔
for (jsize i = 0; i < count; i++) {
ScopedLocalRef name(
env, static_cast(env->GetObjectArrayElement(names, i)));
const char* utf = env->GetStringUTFChars(name.get(), nullptr);
if (utf == nullptr) return;
LogName(utf);
env->ReleaseStringUTFChars(name.get(), utf);
}
ہر تکرار ہے۔ ScopedLocalRef صف کے عناصر کے لیے، ڈسٹرکٹر کو تکرار کے اختتام پر یا شروع میں عمل میں لایا جاتا ہے۔ return. کوئی واضح مواد نہیں ہے۔ DeleteLocalRef اگر آپ اسے کہیں سے بھی کال کرتے ہیں اور بعد میں واپسی کا دوسرا راستہ شامل کرتے ہیں تو آپ کو رساو نہیں ہوگا۔
عالمی حوالہ جات ایک اہم فرق کے ساتھ ایک ہی خیال کی پیروی کرتے ہیں۔ ڈسٹرکٹرز ریفرنس بنانے والے سے مختلف تھریڈ پر چل سکتے ہیں۔ JNIEnv*. بجائے، JavaVM* جب آپ کو کسی حوالہ کو حذف کرنے کی ضرورت ہوتی ہے، تو آپ کو موجودہ تھریڈ کا ماحول ملتا ہے۔
class ScopedGlobalRef {
public:
ScopedGlobalRef(JNIEnv* env, jobject obj)
: ref_(obj ? env->NewGlobalRef(obj) : nullptr) {
env->GetJavaVM(&vm_);
}
~ScopedGlobalRef() {
JNIEnv* env = nullptr;
if (ref_ != nullptr &&
vm_->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) == JNI_OK) {
env->DeleteGlobalRef(ref_);
}
}
ScopedGlobalRef(const ScopedGlobalRef&) = delete;
ScopedGlobalRef& operator=(const ScopedGlobalRef&) = delete;
jobject get() const { return ref_; }
private:
JavaVM* vm_ = nullptr;
jobject ref_;
};
کنسٹرکٹر کسی بھی مقامی یا عالمی حوالہ سے عالمی حوالہ تخلیق کرتا ہے اور اسے ریکارڈ کرتا ہے۔ JavaVM*. تباہ کن اسے کہتے ہیں۔ GetEnv حاصل کرنے کے لئے JNIEnv* یہ صرف حوالہ جات کو حذف کرتا ہے اگر یہ کسی تھریڈ میں چلتا ہے اور وہ تھریڈ JVM سے منسلک ہے۔
اگر ڈسٹرکٹر غیر منسلک دھاگے پر چلتا ہے، تو یہ سادہ ورژن کریش ہونے کی بجائے ایک حوالہ لیک کر دے گا۔ پروڈکشن ورژن آپ کو عارضی طور پر منسلک کرکے یا لیک کو لاگ کرکے مطلع کرے گا۔ اینڈرائیڈ کا پلیٹ فارم کوڈ خود اسی پیٹرن کا استعمال کرتا ہے۔ ScopedLocalRef اگر آپ خود لکھنا نہیں چاہتے ہیں تو مددگار اور fbjni جیسی لائبریریاں دونوں ریپرز کے مکمل ٹیسٹ شدہ ورژن فراہم کرتی ہیں۔
حوالہ کی اقسام کا موازنہ کریں۔
| جائیداد | مقامی | عالمی | کمزور عالمی |
|---|---|---|---|
| پوسٹ کردہ بذریعہ: | زیادہ تر JNI افعال | NewGlobalRef |
NewWeakGlobalRef |
| میعاد ختم ہونے کی مدت | مقامی طریقہ واپسی | DeleteGlobalRef |
DeleteWeakGlobalRef |
| دوسرے دھاگوں کے ذریعہ استعمال کیا جاسکتا ہے۔ | نہیں | ہاں | ہاں |
| آبجیکٹ کو زندہ رکھنا | ہاں | ہاں | نہیں |
| خود کار طریقے سے جاری | ہاں واپسی یا علیحدگی پر | نہیں | نہیں |
| عام استعمال | ایک کال میں عارضی قدر | کیشڈ کلاسز، ملکیتی کال بیکس | ان اشیاء کے لیے کال بیکس جن کی ملکیت مقامی کوڈ کے پاس نہیں ہونی چاہیے۔ |
جدول تین حوالوں کی اقسام کا خلاصہ کرتا ہے۔ "بہترین پہلے” لائن بیان کرتی ہے کہ ہینڈل کب استعمال نہیں کیا جائے گا، اور "آٹو ریلیز” لائن یہ بتاتی ہے کہ آیا JVM کا استعمال کرتے ہوئے ہینڈل کو صاف کیا جا سکتا ہے۔
اہم بات یہ ہے کہ مقامی حوالہ جات ہی واحد قسم ہیں جسے JVM صاف کرتا ہے، اور یہ سہولت آپ کو مقامی حوالہ جات کو ذخیرہ کرنے یا شیئر کرنے سے روکتی ہے۔ عالمی حوالہ جات کو ذخیرہ اور اشتراک کیا جا سکتا ہے، لیکن ہر حوالہ ایک مستقل GC جڑ ہے جب تک کہ حذف نہ ہو جائے۔ ایک کمزور عالمی حوالہ آبجیکٹ کو بغیر پن لگائے اسے اسٹور اور شیئر کرنے کی اجازت دیتا ہے، لیکن جب بھی اسے استعمال کیا جائے تو اسے مقامی حوالہ میں فروغ دیا جانا چاہیے۔
خلاصہ
ہر JNI حوالہ JVM مینجمنٹ ٹیبل میں ایک سلاٹ کا ہینڈل ہوتا ہے، اور ہر حوالہ کی قسم کی وضاحت اس وقت سے ہوتی ہے جب اس سلاٹ کو آزاد کیا جاتا ہے۔ جب ایک مقامی طریقہ واپس آتا ہے اور ایک ہی دھاگے سے تعلق رکھتا ہے، تو مقامی حوالہ جات ضائع ہو جاتے ہیں۔ عالمی حوالہ جات اس وقت تک باقی رہتے ہیں جب تک کہ حذف نہ ہو جائے اور اسے کہیں بھی استعمال کیا جا سکتا ہے۔ کمزور عالمی حوالہ جات حذف ہونے تک برقرار رہتے ہیں، لیکن اعتراض کو زندہ نہیں رکھتے۔
زیادہ تر JNI تنازعات ان اصولوں میں سے کسی ایک کی خلاف ورزی کا براہ راست نتیجہ ہیں۔ اگر آپ اسے آزاد کیے بغیر لوپ میں مقامی حوالہ بناتے ہیں، تو مقامی جدول بہہ جائے گا۔ DeleteLocalRef یا PushLocalFrame اور PopLocalFrame روکیں اگر آپ بعد میں استعمال کے لیے مقامی حوالہ محفوظ کرتے ہیں، تو آپ کے پاس ایک باسی ہینڈل رہ جائے گا۔ NewGlobalRef اصلاح عالمی حوالہ جات کو حذف کیے بغیر تخلیق کرنے سے میموری لیک ہو جائے گی اور بالآخر عالمی جدول کو اوور فلو کر دیا جائے گا۔ پہلے ان کی تشہیر کیے بغیر کمزور حوالوں کا استعمال کرنا NewLocalRef کوڑا اٹھانے والے سے مقابلہ کرتا ہے۔
تھریڈنگ کے اصول اتنے ہی اہم ہیں۔ کوئی راستہ نہیں JNIEnv* کیشے کیونکہ اس کا تعلق ایک دھاگے سے ہے۔ JavaVM* JNI استعمال کرنے سے پہلے، ہر مرکزی دھاگے کو جوڑیں اور پھر دھاگے کے باہر ہونے سے پہلے الگ کریں۔ ایپلیکیشن کلاس ریزولوشن JNI_OnLoadکیونکہ مقامی دھاگہ بعد میں اسے تلاش نہیں کر سکے گا۔ زیر التواء مستثنیات کو چیک کریں اور آنے والی ہر کال کے بعد حوالہ جات کا موازنہ کریں۔ IsSameObject بلکہ ==.
آخر میں، ٹولز کے ذریعے ان اصولوں کو لاگو کرنے کی کوشش کریں۔ Android پر CheckJNI کا استعمال کرتے ہوئے ٹیسٹ چل رہا ہے۔ -Xcheck:jni حوالہ جاتی غلطیوں کا سامنا کرنے والی کالوں پر غلطیوں سے بچنے کے لیے، ہم RAII ریپر کا استعمال کرتے ہیں تاکہ یہ یقینی بنایا جا سکے کہ ڈیریفرنس خود بخود واقع ہو جائے، بجائے اس کے کہ ڈیسک ٹاپ پر ڈیریفرنس کو یاد رکھنے کے لیے ہر کوڈ کے راستے پر انحصار کریں۔