Question

I have my JNI environment and jobject objects saved locally as of now. I found that for my JNI to run of ICS and up devices, I need to fix my JNI code. This is the error I get:

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

I am confused about how to create/destroy these globals, and if I am even doing it right.

My application currently runs fine on all pre-ICS devices using this code:

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;
    ...
}

Under the GetStaticMethodID calls, the RelayAPI_ variables are all function pointers that lead here:

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

Finally, it returns to my Java code here:

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

I have changed my initializations to:

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

But i'm extremely confused with this, as they have the same parameters, and it just doesn't make sense. I can't find documentation for this method anywhere as stupid as that sounds, this tutorial, this page, and the oracle docs don't have any information on the NewGlobalRef method itself.

EDIT

jmethodID changeID;
jmethodID getID;
jmethodID putID;
jmethodID flushID;
jmethodID delayID;
jobject myObject;
jclass    bluetoothClass;
JNIEnv *myEnv;
Was it helpful?

Solution

First of all: myEnv = (*env)->NewGlobalRef(env,obj); is just wrong. You mustn't cache this value.

What you're allowed to is to cache method IDs, field IDs, class references, ... (but make sure you clean up this stuff afterwards). But caching these values requires special measures.

Why? The problem is that the JVM is allowed to load and unload classes according to the needs of the program. Therefore it could happen that a class is unloaded as soon as the last instance of the class has been destroyed by the garbage collector. As soon as this happens your cached IDs are not valid anymore. It's possible that the IDs will be the same after the JVM loads the class again but this is not guaranteed.

Solution: If you want to cache these IDs you have to tell the JVM that it's not allowed to unload a class. This is exactly what NewGlobalRef does. You just increment the reference for the reference passed to NewGlobalRef so the reference count never drops to zero and grabage collection is not allowed to clean up the referenced element.

Attention: Creating a NewGlobalRef has a serious drawback: Other than in Java you have to make sure that you call DeleteGlobalRef if you don't need this reference anymore in order to reenable the garbage collection of the reference. (As the garbage collecter is not aware of wether you still need this reference or not) Or in other words: You have to make sure that you cleanup your garbage yourself otherwise you'll leave a memory leak.

I'd also say it's not a good idea to create a global ref for an object (unless you really want to keep the object alive) as this means the object won't ever get into garbage and therefore never will be freed.

Better Variant: If you want to cache these IDs in order to speedup access to a certain object, keep a global reference for the class (using FindClass) and grab the IDs from the class object FindClass returns.

Here's a (incomplete) example of what I mean. I usually create a structure holding all the IDs I need to access a class just to keep my namespaces clean. You can imagine this as follows:

/*! \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 };

The easiest way is to provide a "connect" function for your object which does the initialization of the structure defined above.

/*! \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;
}

After calling MyClass_connect successfully you can use me.aField to shorten the code to access a the field in your code. Of course you have to provide a disconnect function which is called when MyClass is not required anymore:

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

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

Sorry for this a bit lengthy posting but I hope this helps to solve your confusion a bit and gives you a bit an insight of the inner workings of JNI together with a little receipe how to deal with this efficiently.

Edit: You can find documentation about JNI calls on oracle's website

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top