Почему я не должен повторно использовать jclass и / или jmethodID в JNI? - PullRequest
35 голосов
/ 19 января 2010

Это вопрос, связанный с предыдущим постом , но этот пост был решен, и теперь я хотел изменить направление вопроса.

При работе с JNI необходимозапросить у JNIEnv объекта jclass и jmethodID для каждого класса и метода, которые будут использоваться в коде C / C ++.Просто чтобы прояснить, я хочу вызывать конструкторы или методы Java из C / C ++.

Поскольку обмен данными из Java в C / C ++ (и наоборот) является дорогостоящим, я изначально думал, что один из способов минимизировать этоповторно использовать 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 для каждой функции JNI?

Ответы [ 5 ]

58 голосов
/ 19 января 2010

Правила здесь понятны. Значения идентификатора метода и поля всегда. Вы можете повесить на них. Поиск занимает некоторое время.

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();
    }
}
7 голосов
/ 19 января 2010

Внутри JNI_OnLoad, вам нужно использовать NewGlobalRef для значений jclass, возвращаемых FindClass, прежде чем их кэшировать.

Затем внутри JNI_OnUnload вы вызываете DeleteGlobalRef для них.

5 голосов
/ 09 февраля 2017

Как уже писали другие

  1. Вы можете без проблем хранить jmethodID в статической переменной C ++
  2. Вы можете хранить локальные jobject или jclass в статической переменной C ++ после преобразования их в глобальные объекты, вызывая env->NewGloablRef()

Я просто хочу добавить дополнительную информацию здесь: Основная причина хранения jclass в статической переменной C ++ заключается в том, что вы думаете, что это проблема с производительностью - каждый раз вызывать env->FindClass().

Но я измерил скорость из FindClass() с помощью счетчика производительности с QueryPerformanceCounter() API в Windows. И результат был удивительным:

На моем компьютере с процессором 3,6 ГГц исполнение

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

занимает от 0,01 мс до 0,02 мс. Это невероятно быстро. Я посмотрел в исходный код Java, и они используют словарь, где хранятся классы. Кажется, это реализовано очень эффективно.

Я протестировал еще несколько классов и вот результат:

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

Приведенные выше значения взяты из потока диспетчера событий Java. Если я выполню тот же код в собственном потоке Windows, который был создан мной CreateThread(), он будет работать даже в 10 раз быстрее . Почему?

Так что, если вы не вызываете FindClass() очень часто, то нет абсолютно никаких проблем с вызовом его по требованию при вызове вашей функции JNI вместо создания глобальной ссылки и сохранения ее в статической переменной.


Другая важная тема - thread thread . В Java каждый поток имеет свою собственную независимую JNIEnv структуру.

  1. Global jobject или jclass действительны в любом потоке Java.
  2. Локальные объекты действительны только в одном вызове функции в JNIEnv вызывающего потока и собирают мусор, когда код JNI возвращается в Java.

Теперь это зависит от потоков, которые вы используете: если вы регистрируете свою функцию C ++ с помощью env->RegisterNatives() и Java-код вызывает ваши функции JNI, то вы должны хранить все объекты, которые вы хотите использовать позже, как глобальные объекты в противном случае они будут собирать мусор.

Но если вы создадите свой собственный поток с помощью CraeteThread() API (в Windows) и получите структуру JNIEnv, вызвав AttachCurrentThreadAsDaemon(), тогда будут применяться совершенно другие правила: поскольку это ваш собственный поток, он никогда не возвращает управление В Java сборщик мусора никогда не очистит созданные вами объекты в потоке, и вы даже можете без проблем хранить локальные объекты в статических переменных C ++! (Но к ним нельзя получить доступ из других потоков). В этом случае крайне важно, чтобы вы очистили ВСЕ ваши локальные экземпляры вручную с помощью env->DeleteLocalRef(), иначе у вас будет утечка памяти.

Я настоятельно рекомендую загрузить ВСЕ локальные объекты в класс-оболочку, который вызывает DeleteLocalRef() в своем деструкторе. Это пуленепробиваемый способ избежать утечек памяти.


Разработка кода JNI может быть очень громоздкой, и вы можете получить сбои, которые вы не понимаете. Чтобы найти причину сбоя, откройте окно DOS и запустите приложение Java с помощью следующей команды:

java -Xcheck:jni -jar MyApplication.jar

тогда вы увидите, какие проблемы произошли в коде JNI. Например:

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

и вы найдете трассировку стека в файле журнала, который Java создает в той же папке, где у вас есть файл JAR:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e8655d5, pid=4692, tid=4428
#
etc...
4 голосов
/ 19 января 2010

Как я помню, jclass is будет локальным для вызывающего метода, поэтому не может быть кэширован, однако может быть идентификатор метода. См. http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/design.html для дополнительной информации.

Извините, я не знаю об аспекте производительности, каждый раз, когда я использовал JNI, он был незначительным по сравнению с поставленной задачей.

0 голосов
/ 30 января 2013

Вы можете кэшировать и использовать свои идентификаторы методов / функций / классов и безопасно использовать их, если вы правильно их кодируете. Я ответил на аналогичный вопрос здесь на SO и опубликовал явный код для того, чтобы следовать рекомендациям IBM по производительности кэширования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...