2010-01-19 5 views
31

To jest pytanie związane z a previous post, ale ten post został rozwiązany i teraz chciałem zmienić kierunek pytania.Dlaczego nie powinienem ponownie używać jclass i/lub jmethodID w JNI?

Podczas pracy z JNI należy zadać obiekt JNIEnv dla jclass i jmethodID dla każdej klasy i metody, która będzie używana w kodzie C/C++. Dla jasności chcę wywoływać contruktory lub metody Javy z C/C++.

Ponieważ komunikacja z Java do C/C++ (i viceversa) jest kosztowna, początkowo sądziłem, że jednym ze sposobów na zminimalizowanie tego jest ponowne użycie jclass i jmethodID. Dlatego ja uratowałem to instancje zmiennych globalnych, co następuje:

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 
} 

bardziej specyficznym (i przydatnych) Przykład, który używam rzucać wyjątki z dowolnego miejsca w mojej funkcji JNI:

jclass jniExceptionClass   = NULL; 

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

Problem jest to, że nadal używałem tego wzorca i otrzymałem błąd segmentacji, który został rozwiązany tylko przez nie-ponowne wykorzystanie tych zmiennych (to było rozwiązanie do poprzedniego postu).

Pytania są:

  • Dlaczego jest to niezgodne z prawem do ponownego użycia jclass i jmethodID thru różnych funkcji JNI? Myślałem, że te wartości są zawsze takie same.
  • Tylko dla ciekawości: jaki jest wpływ/narzut inicjowania wszystkich niezbędnych jclass i jmethodID dla każdej funkcji JNI?

Odpowiedz

54

Zasady tutaj są jasne. Identyfikator metody i wartości ID pola są na zawsze. Możesz na nich wisieć. Wyszukiwanie zajmuje trochę czasu.

jclass, z drugiej strony, na ogół jest to lokalny numer referencyjny. Odwołanie lokalne przetrwa najwyżej czas trwania pojedynczego wywołania funkcji JNI.

Jeśli chcesz zoptymalizować, musisz poprosić maszynę JVM, aby wykonała dla ciebie globalną referencję. Często zdarza się, że uzyskujesz i zachowujesz odniesienia do popularnych klas, takich jak java.lang.String.

Przytrzymanie takiego odniesienia do klasy uniemożliwi oczywiście (klasę) zbieranie śmieci.

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); 

czeku wywołania makro:

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 automatycznie czyści lokalnych odniesień zwróconych przez ' FindClass' po powrocie do strony Java. –

+0

w ten sposób "co najwyżej". – bmargulies

+2

w tym łączu (http://java.sun.com/docs/books/jni/html/other.html) używają NewWeakGlobalRef. Spróbuję z słabym odniesieniem. – YuppieNetworking

4

Jak pamiętam, jclass będzie lokalna dla metody wywołującej, więc nie można jej zapisać w pamięci podręcznej, ale może to być identyfikator metody. Aby uzyskać więcej informacji, patrz: http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/design.html.

Przepraszam, nie wiem o aspekcie wydajności, za każdym razem, gdy korzystałem z JNI, było to nieistotne w porównaniu z zadaniem w ręku.

6

Inside JNI_OnLoad, trzeba użyć NewGlobalRef na wartości jclass zwróconych przez FindClass przed ich buforowanie.

Następnie, pod numerem JNI_OnUnload dzwonisz pod numer DeleteGlobalRef.

0

Możesz buforować i używać swoich identyfikatorów metod/funkcji/klas i bezpiecznie z nich korzystać, jeśli odpowiednio je zakodujesz. Odpowiedziałem na a similar question here on SO i napisałem wyraźny kod, aby śledzić zalecenia wydajności buforowania IBM.

3

Jak inni już napisali

  1. Można przechowywać jmethodID w zmiennej statycznej C++ bez problemów
  2. można przechowywać lokalnej jobject lub jclass w zmiennej statycznej C++ po konwersji ich do globalnych obiektów wywołując env->NewGloablRef()

Po prostu chcę dodać dodatkowe informacje tutaj: Głównym powodem przechowywania jclass w statycznej zmiennej C++ wi Będziesz uważał za problem z wydajnością wywoływanie za każdym razem env->FindClass().

Ale I zmierzył prędkość z FindClass() z licznikiem wydajności z interfejsem API QueryPerformanceCounter() w systemie Windows. I wynik był zaskakujący:

Na moim komputerze z procesorem 3,6 GHz, a jest wykonanie

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

trwa między 0,01 i 0,02 ms ms. To jest niesamowicie szybkie. Zajrzałem do kodu źródłowego Javy i wykorzystałem słownik, w którym przechowywane są klasy. Wydaje się, że wdraża się to bardzo skutecznie.

Testowałem trochę więcej klas i oto wynik:

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 

Powyższe wartości są od dyspozytora wydarzenie wątku Java. Jeśli wykonam ten sam kod w natywnym wątku systemu Windows, który został utworzony przez mnie CreateThread(), działa on nawet 10 razy szybciej. Czemu?

Tak więc, jeśli nie wywołuje się często FindClass(), nie ma absolutnie żadnego problemu z wywoływaniem go na żądanie, gdy wywoływana jest funkcja JNI zamiast tworzenia globalnego odniesienia i zapisywania go w zmiennej statycznej.


Innym ważnym tematem jest bezpieczeństwo wątek. W Javie każdy wątek ma własną niezależną strukturę JNIEnv.

  1. Globalne jobject lub jclass są ważne w każdym wątku Java.
  2. Obiekty lokalne są ważne tylko w jednym wywołaniu funkcji w JNIEnv wątku wywołującego i są śmieciami zbierane, gdy kod JNI wraca do Java.

Teraz to zależy od gwintów, że używasz: Jeśli zarejestrujesz swój C funkcję ++ z env->RegisterNatives() i kod Java dzwoni do swoich funkcji JNI następnie trzeba przechowywać wszystkie obiekty, które chcesz użyć później, jako globalny obiekty w przeciwnym razie będą zbierać śmieci.

Ale jeśli stworzysz swój własny wątek z CraeteThread() API (w systemie Windows) i uzyskanie struktury JNIEnv wywołując AttachCurrentThreadAsDaemon() następnie całkowicie zastosuje inne zasady: Jak to jest swój własny wątek, że nigdy nie zwraca sterowanie do Java, śmieci kolekcjoner nigdy nie oczyści obiektów, które utworzyłeś w swoim wątku, a możesz nawet przechowywać lokalne obiekty w statycznych zmiennych C++ bez problemów! (Ale nie można uzyskać do nich dostępu z innych wątków) W takim przypadku bardzo ważne jest, aby ręcznie usunąć WSZYSTKIE lokalne wystąpienia za pomocą env->DeleteLocalRef(), w przeciwnym razie wystąpi przeciek pamięci.

Zdecydowanie zaleca się załadowanie WSZYSTKICH obiektów lokalnych do klasy opakowania, która wywołuje DeleteLocalRef() w swoim destruktorze. Jest to skuteczny sposób na uniknięcie wycieków pamięci.


Opracowanie kodu JNI może być bardzo uciążliwe i może dojść do awarii, których nie rozumiesz. Aby znaleźć przyczynę awarii otworzyć okno DOS i uruchomić aplikację Java za pomocą następującego polecenia:

java -Xcheck:jni -jar MyApplication.jar 

wtedy zobaczysz jakie problemy się stało w kodzie JNI. Na przykład:

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

i znajdą Państwo StackTrace w pliku dziennika, który Java tworzy w tym samym folderze gdzie masz plik JAR:

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