2010-01-19 1 views
31

이것은 a previous post과 관련된 질문이지만이 게시물이 해결되어 이제 질문의 방향을 바꾸고 싶습니다.JNI에서 jclass 및/또는 jmethodID를 재사용하지 않아야하는 이유는 무엇입니까?

하는 JNI 작업을 할 때, C/C++ 코드에서 사용되는 각 클래스 및 방법 jclassjmethodIDJNIEnv 객체를 요청하는 것이 필요하다. 그냥 명확히하기 위해 C/C++에서 Java 컨스트럭터 또는 메서드를 호출하려고합니다.

Java에서 C/C++ (및 viceversa) 통신은 비용이 많이 들기 때문에 처음에는이 문제를 최소화하는 한 가지 방법은 jclassjmethodID을 다시 사용하는 것이라고 생각했습니다. 내 JNI 함수에 어디에서 예외를 발생하는 데 사용

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 
} 

보다 구체적인 (유용한) 예 :

jclass jniExceptionClass   = NULL; 

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

문제를 다음과 같이 따라서, 나는 전역 변수에서이 인스턴스를 저장 내가이 패턴을 계속 사용했고이 변수를 재사용하지 않음으로써 해결 된 세분화 오류가 발생했습니다 (이것은 이전 게시물의 해결책이었습니다).

질문은 다음과 같습니다

  • 왜 다른 JNI 함수 스루 jclassjmethodID를 다시 사용하는 것은 불법입니다? 나는이 값이 항상 같다고 생각했다.
  • 호기심에 대한 : 각 JNI 기능에 필요한 모든 파일을 초기화하는 데 미치는 영향은 무엇입니까? jclassjmethodID?

답변

54

여기 규칙이 명확합니다. 메소드 ID와 필드 ID 값은 영원합니다. 너는 그들 위에 매달릴 수있다. 조회에는 약간의 시간이 걸립니다. 한편,

jclass은 일반적으로 로컬 참조입니다. 로컬 참조는 JNI 함수에 대한 단일 호출의 지속 시간을 유지합니다.

최적화가 필요한 경우 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가 자동으로'에 의해 반환되는 로컬 참조를 정리 : 자바 당신이 JAR 파일이 같은 폴더에 생성 로그 파일의 스택 트레이스를 찾을 수

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

및 예를 들면 : FindClass' 자바 측으로 돌아갈 때. –

+0

따라서 '최대' 이 링크 (http://java.sun.com/docs/books/jni/html/other.html)의 – bmargulies

+2

은 NewWeakGlobalRef를 사용합니다. 나는 약한 참조로 시도 할 것이다. – YuppieNetworking

4

jclass는 호출하는 메소드에 로컬이 될 것이므로 캐싱 할 수는 없지만 메소드 ID는 될 수 있습니다. 자세한 내용은 http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/design.html을 참조하십시오.

미안하지만 성능 측면에 대해서는 잘 모르겠다. JNI를 사용할 때마다 당면한 업무에 비해 중요하지 않았다.

6

JNI_OnLoad 내부는, 당신이 그들을 캐싱하기 전에 FindClass에 의해 반환 된 jclass 값에 대한 NewGlobalRef를 사용해야합니다.

JNI_OnUnload 안에 DeleteGlobalRef을 부릅니다.

0

메서드/함수/클래스 ID를 캐시하고 사용할 수 있으며 적절하게 코드를 작성하면 안전하게 사용할 수 있습니다. 나는 a similar question here on SO으로 대답했으며 IBM의 캐싱 성능 권장 사항을 따르는 방법에 대한 명시적인 코드를 게시했습니다.

3

다른 사람이 이미 문제없이

  1. 당신은 정적 C++ 변수에 jmethodID를 저장할 수를 썼던 것처럼
  2. 당신은 jobject 또는 정적 C++ 변수에 jclassenv->NewGloablRef()
  3. 를 호출하여 전역 개체로 변환 후 로컬에 저장할 수 있습니다

그냥 여기에 추가 정보를 추가하고 싶습니다 : 정적 C++ 변수 wi에 jclass를 저장하는 주요 이유 매번 env->FindClass() 번으로 전화하는 것이 성능 문제라고 생각하십니까?

그러나 은 Windows에서 QueryPerformanceCounter() API를 사용하는 성능 카운터를 사용하여FindClass()으로 측정했습니다. 3,6 GHz의 CPU와 컴퓨터

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

의 실행 0.01 MS 0.02 MS 사이에 필요에

: 그리고 그 결과는 놀라웠다. 그것은 엄청나게 빠릅니다. Java 소스 코드를 살펴본 후 클래스가 저장되어있는 Dictionary를 사용합니다. 이것은 매우 효율적으로 구현되는 것 같습니다.

는 좀 더 클래스를 테스트하고 여기 결과입니다

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()에 의해 생성 된 네이티브 Windows 스레드에서 동일한 코드를 실행하면 도 더 빨리 실행됩니다.. 왜?

FindClass()을 매우 자주 호출하지 않으면 전역 참조를 만든 다음 정적 변수에 저장하는 대신 JNI 함수를 호출 할 때 필요시 호출 할 수 있습니다.


또 다른 중요한 주제 스레드 안전입니다. Java에서 각 스레드는 자신의 독립적 인 JNIEnv 구조를가집니다.

  1. jobject 또는 jclass은 모든 Java 스레드에서 유효합니다.
  2. 로컬 개체는 호출 스레드의 JNIEnv에있는 하나의 함수 호출에서만 유효하며 JNI 코드가 Java로 반환 될 때 가비지 수집됩니다.당신이 세계로, 나중에 사용하려면, 당신은 env->RegisterNatives()와 C++ 함수를 등록하고 자바 코드는 다음 모든 개체를 저장해야합니다 JNI 함수로 호출하는 경우 :

지금 당신이 사용하는 스레드에 따라 달라집니다 그렇지 않으면 가비지 수집됩니다.

하지만 당신은 (윈도우에서) CraeteThread() API를 사용하여 자신의 스레드를 생성하고 AttachCurrentThreadAsDaemon()를 호출하여 JNIEnv 구조를 얻을 경우 다음 완전히 다른 규칙이 적용됩니다 : 그것은 자바, 쓰레기로 제어를 반환하지 않습니다 자신의 스레드이기 때문에 컬렉터는 쓰레드에서 생성 한 객체를 결코 지울 수 없으며 로컬 객체를 문제없이 정적 C++ 변수에 저장할 수도 있습니다! (하지만 다른 스레드에서는 액세스 할 수 없습니다.)이 경우에는 env->DeleteLocalRef()으로 모든 로컬 인스턴스를 수동으로 정리해야합니다. 그렇지 않으면 메모리 누수가 발생합니다.

모든 로컬 객체를 소멸자에 DeleteLocalRef()을 호출하는 래퍼 클래스에로드하는 것이 좋습니다. 이것은 메모리 누출을 피하는 총알이없는 방법입니다.


JNI 코드를 개발하는 것은 매우 복잡 할 수 있으며 사용자가 이해하지 못하는 충돌이 발생할 수 있습니다.

java -Xcheck:jni -jar MyApplication.jar 

은 다음 JNI 코드에서 일어난 어떤 문제가 표시됩니다 충돌의 원인은 DOS 창을 열고 다음 명령을 사용하여 Java 응용 프로그램을 시작 찾을 수 있습니다.

# 
# A fatal error has been detected by the Java Runtime Environment: 
# 
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e8655d5, pid=4692, tid=4428 
# 
etc... 
관련 문제