Managing references in JNI

JNI exposes strings, classes, instance objects, and arrays as reference types. The previous recipe introduces the string type. This recipe will cover reference management and the subsequent three recipes will discuss class, object, and arrays respectively.

How to do it…

The following steps create a sample Android project that illustrates reference management in JNI:

  1. Create a project named ManagingReference. Set the package name as cookbook.chapter2. Create an activity named ManagingReferenceActivity. Under the project, create a folder named jni. Refer to the Loading native libraries and registering native methods recipe in this chapter, if you want more detailed instructions.
  2. Create a file named referencetest.c under the jni folder, then implement the localReference, globalReference, weakReference, and referenceAssignmentAndNew methods. This is shown in the following code snippet:
    JNIEXPORT void JNICALL Java_cookbook_chapter2_ManagingReferenceActivity_localReference(JNIEnv *pEnv, jobject pObj, jstring pStringP, jboolean pDelete){
        jstring stStr;
      int i;
      for (i = 0; i < 10000; ++i) {
        stStr = (*pEnv)->NewLocalRef(pEnv, pStringP);
        if (pDelete) {
          (*pEnv)->DeleteLocalRef(pEnv, stStr);
        }
      }
    }
    
    JNIEXPORT void JNICALL Java_cookbook_chapter2_ManagingReferenceActivity_globalReference(JNIEnv *pEnv, jobject pObj, jstring pStringP, jboolean pDelete){
      static jstring stStr;
      const jbyte *str;
      jboolean *isCopy;
      if (NULL == stStr) {
        stStr = (*pEnv)->NewGlobalRef(pEnv, pStringP);
      }
      str = (*pEnv)->GetStringUTFChars(pEnv, stStr, isCopy);
      if (pDelete) {
        (*pEnv)->DeleteGlobalRef(pEnv, stStr);
        stStr = NULL;
      }
    }
    
    JNIEXPORT void JNICALL Java_cookbook_chapter2_ManagingReferenceActivity_weakReference(JNIEnv *pEnv, jobject pObj, jstring pStringP, jboolean pDelete){
      static jstring stStr;
      const jbyte *str;
      jboolean *isCopy;
      if (NULL == stStr) {
        stStr = (*pEnv)->NewWeakGlobalRef(pEnv, pStringP);
      }
      str = (*pEnv)->GetStringUTFChars(pEnv, stStr, isCopy);
      if (pDelete) {
        (*pEnv)->DeleteWeakGlobalRef(pEnv, stStr);
        stStr = NULL;
      }
    }
  3. Modify the ManagingReferenceActivity.java file by adding code to load the native library, then declare and invoke the native methods.
  4. Modify the res/layout/activity_managing_reference.xml file according to step 8 of the Loading native libraries and registering native methods recipe in this chapter, or the downloaded project code.
  5. Create a file named Android.mk under the jni folder. Refer to step 9 of the Loading native libraries and registering native methods recipe of this chapter, or the downloaded code for details.
  6. Start a terminal, go to the jni folder, and type ndk-build to build the native library.
  7. Run the project on an Android device or emulator and monitor the logcat output with either eclipse or the adb logcat -v time command in your terminal. We'll show the sample results for each native method when while going through the details in the following section.

How it works…

This recipe covers reference management in JNI:

  • JNI reference: JNI exposes strings, classes, instance objects, and arrays as references. The basic idea of a reference can be illustrated using the following diagram:
    How it works…

    The reference adds one more level of indirection to an object (an object can be a class, an instance object, a string, or an array) access. An object is pointed by an object pointer, and a reference is used to locate the object pointer. Although this indirection introduces an overhead for object manipulation, it allows VM to conceal the object pointer from developers. The VM can therefore move the underlying object at runtime memory management and update the object pointer value accordingly, without affecting the reference.

    Note that the garbage collector at VM moves the objects around to achieve cheap memory allocation, bulk de-allocation, reduce heap fragmentation, improve locality, and so on.

    Tip

    A reference does not have to be a pointer. The details of how a reference is used to locate the object pointer are hidden from the developers.

  • Local reference versus global reference versus weak reference: Three different types of references can be created to refer to the same data, namely local reference, global reference, and weak reference. Unless we explicitly create a global or weak reference, JNI operates using a local reference. The following table summarizes the differences between the three different types of references:
 

Creation

Lifespan

Visibility

Garbage collector (GC) behavior for referenced object

Free

Local reference

Default or NewLocalRef

One invocation of the native method. Invalid after native method returns.

Within the thread that created it.

GC won't garbage collect the referenced object.

Automatically freed or call DeleteLocalRef

Global reference

NewGlobalRef

Valid until freed explicitly.

Across multiple threads.

GC won't garbage collect the referenced object.

DeleteGlobalRef

Weak reference

NewGlobalWeakRef

Valid until freed explicitly.

Across multiple threads.

GC can garbage collect the referenced object.

DeleteWeakGlobalRef

We will now take a look at the reference types one by one while referring to the sample source code:

  • Local reference: The native method localReference shows the two basic JNI functions, namely NewLocalRef and DeleteLocalRef. The first function creates a local reference, while the second frees it. Note that normally we don't have to free a local reference explicitly, as it will be automatically freed after the native method returns. However, there are two exceptions. First, if we're creating lots of local references within a native method invocation, we can cause overflow. This is illustrated in our sample method when we pass false to the pDelete input parameter. The following screenshot represents an example of such a scenario:
    How it works…

    The first execution deletes the local reference right after using it, so it's finished fine, while the second doesn't delete the local reference and eventually causes the ReferenceTable overflow.

    Secondly, when we implement a utility function that is called by other native functions, we should not leak any references other than the return value. Otherwise, if the utility function is invoked by a native method many times, it will also cause an overflow issue.

    Tip

    Prior to Android 4.0, the local references were implemented using direct pointers to objects. Furthermore, those direct pointers were never invalidated even after DeleteLocalRef was called. Therefore, programmers can use local references as direct pointers, even after the reference is claimed to be deleted. A lot of JNI code not coded correctly worked due to this design. However, local references have been changed to use an indirection mechanism from Android 4.0 onwards. Hence, the buggy code using local references as direct pointers are going to break in Android 4.0 onwards. It is strongly recommended that you always follow JNI specifications.

  • Global reference: The native method, globalReference, demonstrates a typical usage of a global reference. The global reference is retained when passing false to the pDelete input parameter, since it is a static variable. Next time the method is called, the static global reference will still reference to the same object. Therefore, we don't need to call NewGlobalRef again. This technique can save us from carrying out the same operation at every invocation of global reference.

    We invoke globalReference three times in the Java code, as follows:

    globalReference("hello global ref", false); 
    globalReference("hello global ref 2", true);
    globalReference("hello global ref 3", true);

    The results should look similar to the following:

    How it works…

    The string passed along with the first method call is retained, and therefore the first two invocations display the same string. After we delete the global reference at the end of the second call, the third call displays the string passed along with it.

    Note that although DeleteGlobalRef frees the global reference, it doesn't set it to NULL. We have explicitly set the global reference to NULL after the deletion.

  • Weak reference: Weak reference is similar to global reference, except that it doesn't prevent the Garbage Collector (GC) from garbage collecting the underlying object referenced by it. Weak reference is not used as often as local and global reference. A typical use case is when we are referencing to lots of non-critical objects, and we don't want to prevent the GC from garbage collecting some of those objects when the GC thinks it's necessary.

    Tip

    The support for weak references in Android is version dependent. Prior to Android 2.2, weak references were not implemented at all. Prior to Android 4.0, it can only be passed to NewLocalRef, NewGlobalRef, and DeleteWeakGlobalRef. From Android 4.0 onwards, Android has full support for weak references.

  • Assignment versus New<ReferenceType>Ref: In the referencetest.c source code, we implemented the native ReferenceAssignmentAndNew method. This method illustrates the difference between assignment and allocating a new reference.

    We passed the input jstring pStringP to the JNI function NewGlobalRef twice, to create two global references (globalRefNew and globalRefNew2), and assigned one of the global references to a variable globalRefAssignment. We then tested if they were all referencing to the same object.

    Since jobject and jstring are actually pointers to void data type, we can print out their values as integers. Lastly, we invoked DeleteGlobalRef three times. The following is a screenshot of the Android logcat output:

    How it works…

    The first three lines indicate that the input jstring pStringP, two global references globalRefNew and globalRefNew2, and the assigned jstring globalRefAssignment all reference to the same object. Lines five to eight of the output show the same value, which means all the references themselves are equivalent. Lastly, the first two calls of DeleteGlobalRef succeed, while the last one fails.

    The New<ReferenceType>Ref JNI function actually locates the underlying object and then adds a reference to the object. It allows multiple references added for the same object. Note that although our sample execution shows the values of references created by New<ReferenceType>Ref are the same, it is not guaranteed. It is possible that two object pointers pointing to the same object and references referencing to the same object are associated with the two different pointers.

    It is recommended that you never rely on the value of a reference; you should use JNI functions instead. One example of this is to use IsSameObject and never use "==" to test if two references point to the same underlying object unless we test against NULL.

    The number of Delete<ReferenceType>Ref calls must match the number of New<ReferenceType>Ref invocations. Fewer calls will potentially cause a memory leak, while having more calls will fail, as shown in the preceding result.

    The assignment operation doesn't go through the VM, therefore it won't cause the VM to add a new reference.

    Note that although we used a global reference for illustration, the principles also apply to local and weak references.

There's more…

There's another method to manage the local references with JNI functions PushLocalFrame and PopLocalFrame. Interested readers can refer to the JNI specification for more information.

After attaching a native thread with AttachCurrentThread, the code running in the thread would not free the local references until the thread is detached. The local reference should be freed explicitly. In general, it is a good practice that we free local reference explicitly, as long as we don't need it any more.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset