2010-01-19 4 views
31

यह a previous post से संबंधित एक प्रश्न है, लेकिन इस पोस्ट को हल किया गया था और अब मैं प्रश्न की दिशा बदलना चाहता हूं।मुझे जेएनआई में एक jclass और/या jmethodID का पुन: उपयोग क्यों नहीं करना चाहिए?

जब JNI साथ काम करना, यह jclass और jmethodID प्रत्येक वर्ग और विधि है कि C/C++ कोड में उपयोग किया जाएगा के लिए के लिए JNIEnv वस्तु पूछने के लिए आवश्यक है। बस स्पष्ट होने के लिए, मैं जावा नियंत्रकों या सी/सी ++ से विधियों को कॉल करना चाहता हूं।

C/C++ (और viceversa) के लिए जावा से संचार के बाद से महंगा है, मैं शुरू में सोचा था कि एक तरह से कम करने के लिए इस jclass और jmethodID पुन: उपयोग किया गया था।

jclass someClass = NULL; 
jmethodID someMethod = NULL; 

JNIEXPORT jobject JNICALL Java_example_method1(JNIEnv *env, jobject jobj) { 
    // initialize someClass and someMethod if they are NULL 
    // use someClass and someMethod to call java (for example, thru NewObject) 
} 

JNIEXPORT jobject JNICALL Java_example_method2(JNIEnv *env, jobject jobj) { 
    // initialize someClass and someMethod if they are NULL 
    // use someClass and someMethod to call java again 
} 

एक और अधिक विशिष्ट (और उपयोगी) उदाहरण के लिए, जो मैं अपने JNI कार्यों में कहीं से भी अपवाद फेंकने के लिए उपयोग करें::

jclass jniExceptionClass   = NULL; 

void throwJavaException(JNIEnv *env, const char* msg) { 
    if (!jniExceptionClass) { 
     jniExceptionClass = env->FindClass("example/JNIRuntimeException"); 
    } 
    if (jniExceptionClass) 
     env->ThrowNew(jniExceptionClass, msg); 
    } 
} 

समस्या इसलिए, मैं वैश्विक चर में इस उदाहरणों इस प्रकार के रूप में सहेजा यह है कि मैंने इस पैटर्न का उपयोग जारी रखा और एक सेगमेंटेशन गलती प्राप्त की जिसे केवल इस चर का पुन: उपयोग करके हल किया गया था (यह पिछली पोस्ट का समाधान था)।

प्रश्न हैं:

  • क्यों यह पुन: उपयोग करने jclass और jmethodID अलग JNI कार्यों के माध्यम से गैर कानूनी है? मैंने सोचा कि यह मान हमेशा एक ही थे।
  • सिर्फ जिज्ञासा के लिए: प्रत्येक जेएनआई फ़ंक्शन के लिए सभी आवश्यक jclass और jmethodID प्रारंभ करने का प्रभाव/ओवरहेड क्या है?

उत्तर

54

यहां नियम स्पष्ट हैं। विधि आईडी और फ़ील्ड आईडी मान हमेशा के लिए हैं। आप उन पर लटका सकते हैं। लुकअप में कुछ समय लगता है।

jclass, दूसरी ओर, आमतौर पर स्थानीय संदर्भ है। एक स्थानीय संदर्भ, जेएनआई समारोह में एक कॉल की अवधि, अधिकतर जीवित रहता है।

यदि आपको अनुकूलित करने की आवश्यकता है, तो आपको JVM से आपके लिए वैश्विक संदर्भ बनाने के लिए कहा जाना चाहिए। java.lang.String जैसे सामान्य वर्गों के संदर्भ प्राप्त करना और संदर्भ रखना असामान्य नहीं है।

कक्षा के इस तरह के संदर्भ को पकड़ना इसे (कक्षा) को कचरा-संग्रहित होने से रोक देगा।

jclass local = env->FindClass(CLS_JAVA_LANG_STRING); 
_CHECK_JAVA_EXCEPTION(env); 
java_lang_string_class = (jclass)env->NewGlobalRef(local); 
_CHECK_JAVA_EXCEPTION(env); 
env->DeleteLocalRef(local); 
_CHECK_JAVA_EXCEPTION(env); 

जांच मैक्रो कॉल:

static inline void 
check_java_exception(JNIEnv *env, int line) 
{ 
    UNUSED(line); 
    if(env->ExceptionOccurred()) { 
#ifdef DEBUG 
     fprintf(stderr, "Java exception at rlpjni.cpp line %d\n", line); 
     env->ExceptionDescribe(); 
    abort(); 
#endif 
     throw bt_rlpjni_java_is_upset(); 
    } 
} 
+0

JNI स्वचालित रूप से स्थानीय 'द्वारा लौटाए गए संदर्भों को साफ:

FATAL ERROR in native method: Bad global or local ref passed to JNI 

और आपको लगता है कि जावा जहां JAR फ़ाइल है एक ही फ़ोल्डर में बनाता है लॉग फ़ाइल में स्टैकट्रेस मिलेगा: उदाहरण के लिए जावा पक्ष पर वापस जाने पर FindClass'। –

+0

इस प्रकार, 'अधिकतर'। इस लिंक में – bmargulies

+2

(http://java.sun.com/docs/books/jni/html/other.html), वे NewWeakGlobalRef का उपयोग करते हैं। मैं कमजोर संदर्भ के साथ कोशिश करेंगे। – YuppieNetworking

4

जैसा कि मुझे याद है कि jclass कॉलिंग विधि के लिए स्थानीय होगा इसलिए कैश नहीं किया जा सकता है, हालांकि विधि आईडी हो सकती है। अधिक विस्तृत जानकारी के लिए http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/design.html देखें।

क्षमा करें, मुझे प्रदर्शन पहलू के बारे में पता नहीं है, किसी भी समय मैंने जेएनआई का उपयोग किया है, यह काम के मुकाबले कम महत्वहीन रहा है।

6

JNI_OnLoad के अंदर, आप उन्हें कैशिंग से पहले FindClass द्वारा दिया jclass मूल्यों पर NewGlobalRef उपयोग करने के लिए की जरूरत है।

फिर, JNI_OnUnload के अंदर आप DeleteGlobalRef पर कॉल करते हैं।

0

आप अपनी विधि/फ़ंक्शन/कक्षा आईडी को कैश और उपयोग कर सकते हैं और यदि आप उचित तरीके से कोड करते हैं तो सुरक्षित रूप से उनका उपयोग कर सकते हैं। मैंने a similar question here on SO का उत्तर दिया है और आईबीएम की कैशिंग प्रदर्शन सिफारिशों का पालन करने के लिए स्पष्ट कोड पोस्ट किया है।

3

के रूप में दूसरों को पहले से ही समस्याओं के बिना लिखा

  1. आप एक स्थिर सी ++ चर में jmethodID स्टोर कर सकते हैं
  2. आप jobject या एक स्थिर सी ++ चर में jclassenv->NewGloablRef()
  3. फोन करके वैश्विक वस्तुओं के लिए उन्हें परिवर्तित करने के बाद स्थानीय स्टोर कर सकते हैं

मैं सिर्फ एक अतिरिक्त जानकारी जोड़ना चाहता हूं: एक स्थिर सी ++ परिवर्तनीय वाई में jclass को संग्रहीत करने का मुख्य कारण हो सकता है कि आपको लगता है कि यह हर बार env->FindClass() पर कॉल करने के लिए एक प्रदर्शन समस्या है।

लेकिन मैं गति FindClass() की विंडोज पर QueryPerformanceCounter() एपीआई के साथ एक प्रदर्शन काउंटर के साथ मापा। एक 3,6 गीगा सीपीयू के साथ अपने कंप्यूटर

jcass p_Container = env->FindClass("java/awt/Container"); 

के निष्पादन 0,01 एमएस और 0,02 एमएस के बीच लेता है पर

: और परिणाम आश्चर्यजनक था। यह अविश्वसनीय रूप से तेज़ है। मैंने जावा स्रोत कोड में देखा और वे एक शब्दकोश का उपयोग करते हैं जहां कक्षाएं संग्रहीत की जाती हैं। ऐसा लगता है कि यह बहुत ही कुशलतापूर्वक लागू किया गया है।

मैं कुछ और अधिक कक्षाओं का परीक्षण किया और यहाँ परिणाम है:

Elapsed 0.002061 ms for java/net/URL 
Elapsed 0.044390 ms for java/lang/Boolean 
Elapsed 0.019235 ms for java/lang/Character 
Elapsed 0.018372 ms for java/lang/Number 
Elapsed 0.017931 ms for java/lang/Byte 
Elapsed 0.017589 ms for java/lang/Short 
Elapsed 0.017371 ms for java/lang/Integer 
Elapsed 0.015637 ms for java/lang/Double 
Elapsed 0.018173 ms for java/lang/String 
Elapsed 0.015895 ms for java/math/BigDecimal 
Elapsed 0.016204 ms for java/awt/Rectangle 
Elapsed 0.016272 ms for java/awt/Point 
Elapsed 0.001817 ms for java/lang/Object 
Elapsed 0.016057 ms for java/lang/Class 
Elapsed 0.016829 ms for java/net/URLClassLoader 
Elapsed 0.017807 ms for java/lang/reflect/Field 
Elapsed 0.016658 ms for java/util/Locale 
Elapsed 0.015720 ms for java/lang/System 
Elapsed 0.014669 ms for javax/swing/JTable 
Elapsed 0.017276 ms for javax/swing/JComboBox 
Elapsed 0.014777 ms for javax/swing/JList 
Elapsed 0.015597 ms for java/awt/Component 
Elapsed 0.015223 ms for javax/swing/JComponent 
Elapsed 0.017385 ms for java/lang/Throwable 
Elapsed 0.015089 ms for java/lang/StackTraceElement 

ऊपर मूल्यों जावा घटना डिस्पैचर धागे से कर रहे हैं। यदि मैं एक देशी कोड को उसी कोड को निष्पादित करता हूं जो मेरे द्वारा CreateThread() द्वारा बनाया गया है, तो यह भी 10 गुना तेज चलाता है। क्यूं कर?

तो यदि आप FindClass() पर अक्सर कॉल नहीं करते हैं तो बहुत अधिक समस्या नहीं होती है जब वैश्विक संदर्भ बनाने और इसे स्थिर चर में संग्रहीत करने के बजाय आपके जेएनआई फ़ंक्शन को कॉल किया जाता है।


एक अन्य महत्वपूर्ण विषय धागा सुरक्षा है। जावा में प्रत्येक थ्रेड की अपनी स्वतंत्र JNIEnv संरचना होती है।

  1. वैश्विक jobject या jclass किसी भी जावा सूत्र में मान्य हैं।
  2. स्थानीय ऑब्जेक्ट केवल कॉलिंग थ्रेड के JNIEnv में एक फ़ंक्शन कॉल में मान्य हैं और जब जेएनआई कोड जावा पर वापस आता है तो कचरा इकट्ठा होता है।

अब यह सूत्र है कि आप प्रयोग कर रहे हैं पर निर्भर करता है: आप env->RegisterNatives() के साथ अपने सी ++ समारोह रजिस्टर और जावा कोड अपने JNI कार्यों में बुला रहा है तो आप सभी वस्तुओं की दुकान चाहिए, जिसे आप बाद में उपयोग करने के लिए, वैश्विक रूप में चाहते हैं वस्तुओं को अन्यथा वे कचरा इकट्ठा करेंगे।

लेकिन अगर आप CraeteThread() एपीआई (Windows पर) के साथ अपने स्वयं धागा बना सकते हैं और AttachCurrentThreadAsDaemon() फोन करके JNIEnv संरचना प्राप्त तो पूरी तरह से अन्य नियम लागू होंगे: यह अपने स्वयं के धागे, कि कभी नहीं जावा, कचरा करने के लिए नियंत्रण देता है कलेक्टर कभी भी आपके थ्रेड पर बनाए गए ऑब्जेक्ट्स को साफ़ नहीं करेगा और आप बिना किसी समस्या के स्थिर सी ++ चर में स्थानीय ऑब्जेक्ट्स स्टोर भी कर सकते हैं! (लेकिन इन्हें अन्य धागे से नहीं पहुंचा जा सकता है) इस मामले में यह बेहद जरूरी है कि आप अपने सभी स्थानीय उदाहरण मैन्युअल रूप से env->DeleteLocalRef() के साथ साफ़ करें अन्यथा आपको स्मृति रिसाव होगा।

मैं दृढ़ता से सभी स्थानीय वस्तुओं को एक रैपर वर्ग में लोड करने की अनुशंसा करता हूं जो DeleteLocalRef() को इसके विनाशक में कॉल करता है। मेमोरी लीक से बचने के लिए यह एक बुलेट प्रूफ तरीका है।


विकास JNI कोड बहुत बोझिल हो सकता है और आप दुर्घटनाओं है कि आप समझ में नहीं आता हो सकता है।

java -Xcheck:jni -jar MyApplication.jar 

तो आप क्या समस्याओं JNI कोड में हुआ है देखेंगे: एक दुर्घटना के कारण एक डॉस विंडो खोलें और निम्न आदेश के साथ अपने जावा आवेदन शुरू जानने के लिए।

# 
# A fatal error has been detected by the Java Runtime Environment: 
# 
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e8655d5, pid=4692, tid=4428 
# 
etc... 
संबंधित मुद्दे