Чем глобальные ссылки JNIEnv отличаются от jobject в C?

На данный момент у меня есть среда JNI и объекты проекта, сохраненные локально. Я обнаружил, что для того, чтобы мой JNI запускал устройства ICS и выше, мне нужно исправить мой код JNI. Это ошибка, которую я получаю:

02-20 10:20:59.523: E/dalvikvm(21629): JNI ERROR (app bug): attempt to use stale local reference 0x38100019
02-20 10:20:59.523: E/dalvikvm(21629): VM aborting
02-20 10:20:59.523: A/libc(21629): Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1), thread 21629

Я не понимаю, как создавать/уничтожать эти глобальные переменные, и правильно ли я это делаю.

Мое приложение в настоящее время работает нормально на всех устройствах до ICS, используя этот код:

BYTE Java_my_eti_commander_RelayAPIModel_00024NativeCalls_InitRelayJava( JNIEnv *env, jobject obj  ) {

    myEnv = (env);
    myObject = obj;

    changeID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "changeItJavaWrapper", "(S)V"  );
    getID    = (*myEnv)->GetStaticMethodID( myEnv, myObject, "getItJavaWrapper"   , "(S)S"   );
    putID    = (*myEnv)->GetStaticMethodID( myEnv, myObject, "putItJavaWrapper"   , "(B)V" );
    flushID  = (*myEnv)->GetStaticMethodID( myEnv, myObject, "flushItJavaWrapper" , "()V"   );
    delayID  = (*myEnv)->GetStaticMethodID( myEnv, myObject, "delayItJavaWrapper" , "(S)V"  );

    RelayAPI_SetBaud= WrapSetBaud;
    RelayAPI_get    = WrapGetIt;
    RelayAPI_put    = WrapPutIt;
    RelayAPI_flush  = WrapFlushIt;
    RelayAPI_delay  = WrapDelayIt;
    ...
}

При вызовах GetStaticMethodID все переменные RelayAPI_ являются указателями функций, которые ведут сюда:

void WrapSetBaud( WORD w ) {
    return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, changeID, w );
}

short WrapGetIt( WORD time ) {
    return (*myEnv)->CallStaticShortMethod( myEnv, myObject, getID, time );
}

void WrapPutIt( BYTE buff ) {
    return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, putID, buff );
}

void WrapFlushIt( void ) {
    return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, flushID );
}

void WrapDelayIt( WORD wait ) {
    return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, delayID, wait );
}

Наконец, он возвращается к моему коду Java здесь:

public static void changeItJavaWrapper( short l ) throws IOException {
    mModelService.changeitJava( l );
}

public static void flushItJavaWrapper() {
    mModelService.flushitJava();
}

public static void putItJavaWrapper( byte p ) {
    mModelService.putitJava( p );
}

public static void delayItJavaWrapper( short wait ) {
   mModelService.delayitJava( wait );
}

public static short getItJavaWrapper( short s ) throws IOException {
    return mModelService.getitJava( s );
}

Я изменил свои инициализации на:

myEnv = (*env)->NewGlobalRef(env,obj);
myObject = (*env)->NewGlobalRef(env,obj);

Но я очень запутался с этим, так как у них одинаковые параметры, и это просто не имеет смысла. Я нигде не могу найти документацию по этому методу, как бы глупо это ни звучало, this tutorial, this page, и the oracle docs не есть какая-либо информация о самом методе NewGlobalRef.

ИЗМЕНИТЬ

jmethodID changeID;
jmethodID getID;
jmethodID putID;
jmethodID flushID;
jmethodID delayID;
jobject myObject;
jclass    bluetoothClass;
JNIEnv *myEnv;

person JuiCe    schedule 21.02.2013    source источник


Ответы (1)


Прежде всего: myEnv = (*env)->NewGlobalRef(env,obj); просто неправильно. Вы не должны кэшировать это значение.

Что вам разрешено, так это кэшировать идентификаторы методов, идентификаторы полей, ссылки на классы,... (но убедитесь, что вы очистили этот материал впоследствии). Но кэширование этих значений требует специальных мер.

Почему? Проблема в том, что JVM разрешено загружать и выгружать классы в соответствии с потребностями программы. Поэтому может случиться так, что класс будет выгружен, как только последний экземпляр класса будет уничтожен сборщиком мусора. Как только это произойдет, ваши кэшированные идентификаторы больше не действительны. Возможно, что идентификаторы будут такими же после того, как JVM снова загрузит класс, но это не гарантируется.

Решение. Если вы хотите кэшировать эти идентификаторы, вы должны указать JVM, что ей не разрешено выгружать класс. Это именно то, что делает NewGlobalRef. Вы просто увеличиваете ссылку для ссылки, переданной в NewGlobalRef, поэтому счетчик ссылок никогда не падает до нуля, а сбор захвата не позволяет очищать элемент, на который указывает ссылка.

Внимание: создание NewGlobalRef имеет серьезный недостаток: в отличие от Java вы должны убедиться, что вы вызываете DeleteGlobalRef, если вам больше не нужна эта ссылка, чтобы снова включить сборку мусора ссылки. (Поскольку сборщик мусора не знает, нужна ли вам эта ссылка по-прежнему или нет) Или, другими словами: вы должны убедиться, что очищаете свой мусор самостоятельно, иначе вы оставите утечку памяти.

Я бы также сказал, что не стоит создавать глобальную ссылку для объекта (если только вы действительно не хотите, чтобы объект оставался живым), поскольку это означает, что объект никогда не попадет в мусор и, следовательно, никогда не будет освобожден.

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

Вот (неполный) пример того, что я имею в виду. Обычно я создаю структуру, содержащую все идентификаторы, которые мне нужны для доступа к классу, просто для того, чтобы мои пространства имен оставались чистыми. Вы можете представить это следующим образом:

/*! \brief Holds cached field IDs for MyClass.java */
typedef struct MyClass {
    int loaded;   /*!< Nonzero if the information are valid */

    jclass clazz;    /*!< Holds a global ref for the class */
    jfieldID aField; /*!< Holds the field ID of aField */
}tMyClass;

static tMyClass me = { 0 };

Самый простой способ - предоставить функцию «подключения» для вашего объекта, которая выполняет инициализацию структуры, определенной выше.

/*! \brief This function fetches field IDs for a specific class in order to have
           faster access elsewhere in the code 

    \param env  a valid JNI environment 

    \return
            -  0 if OK
            - <0 if an error occured */
int MyClass_connect(JNIEnv *env)
{
    jobject theClass = env->FindClass("MyClass");
    if (theClass == NULL) goto no_class;

    me.clazz = (jclass) env->NewGlobalRef(theClass);    // make it global to avoid class unloading and therefore
                                                    // invalidating the references obtained.
    if (me.clazz == NULL) goto no_memory;

      me.aField = env->GetFieldID(me.clazz, "aField", "I")    ;
    if (me.aField == NULL) goto no_aField;

    me.loaded = 1;
    return 0;

no_aField:
    env->DeleteGlobalRef(me.clazz);
no_memory:
no_class:
    return -1;
}

После успешного вызова MyClass_connect вы можете использовать me.aField, чтобы сократить код для доступа к полю в вашем коде. Конечно, вы должны предоставить функцию отключения, которая вызывается, когда MyClass больше не требуется:

void MyClass_disconnect(JNIEnv *env)
{
    if (me.loaded == 0) return;

    env->DeleteGlobalRef(me.clazz);
    memset(me, 0, sizeof(tMyClass));
}

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

Изменить: документацию о вызовах JNI можно найти на странице веб-сайт Oracle

person junix    schedule 21.02.2013
comment
Большое спасибо за это. Вы случайно не знаете какие-нибудь хорошие веб-сайты, на которых есть дополнительная информация об этом в C? - person JuiCe; 21.02.2013
comment
Вы ищете информацию о чем именно? JNI? Лучшая практика? - person junix; 21.02.2013
comment
Я все еще немного растерян, так как использую GetStaticMethodID вместо GetFieldID. Чтобы я мог вызвать метод, который я найду позже в своем коде, я также должен иметь глобальную сохраненную JNI Env, показанную во втором блоке кода. -- Я также не знаю, какая у вас переменная tUsb_Device, с чем сравнивается число для этого параметра? - person JuiCe; 22.02.2013
comment
@JuiCe Тип tUsb_Device является ошибкой копирования и вставки. Я взял код из проекта, над которым работаю. Я исправил это. Прости. Что касается функций Get*ID, то в основном все то же самое. Все они извлекают определенные атрибуты класса или объекта. GetStaticMethodID, например, возвращает идентификатор статического метода класса, а GetFieldID дает идентификатор поля в классе и т. д. Я включил ссылку на документацию JNI в свое сообщение. Вы найдете там всю необходимую информацию. Что касается JNIEnv, вы не должны сохранять этот указатель. Как вы думаете, почему вы должны? - person junix; 23.02.2013
comment
Только потому, что я сделал это во втором блоке кода, используя CallStatic*Method. Я не могу найти примеров CallStatic*Method, которые не используют среду JNI в качестве параметра. The docs показать среду там. Я также не очень хорошо знаком с C, поэтому, если я упустил что-то очевидное, извините. - person JuiCe; 23.02.2013
comment
@JuiCe Не нужно извиняться. Может быть, я недостаточно хорошо понимаю ваше приложение. Кто (какой поток работает в какой среде) инициирует вызов функций Wrap*? Крайне важно, чтобы поток, выполняющий вызовы JNI, был подключен к JVM и имел собственный JNIEnv (поскольку это зависит от потока). - person junix; 23.02.2013
comment
функции Wrap* вызываются во всем моем коде C. Код C — это существующая библиотека, которая ранее использовалась для Palm Pilots. Setit/Getit/Putit/Flushit/delayit раньше указывали на поток bluetooth пилота Palm, но теперь все возвращаются к потоку bluetooth конца Java. Если я недостаточно ясен, дайте мне знать. Теперь мой код работает, и это меня сбивает с толку. Я редактирую свой вопрос, как я объявляю переменные в верхней части файла .c. - person JuiCe; 25.02.2013