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.
The following steps create a sample Android project that illustrates reference management in JNI:
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.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; } }
ManagingReferenceActivity.java
file by adding code to load the native library, then declare and invoke the native methods.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.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.jni
folder, and type ndk-build
to build the native library.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.This recipe covers reference management in JNI:
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.
Creation |
Lifespan |
Visibility |
Garbage collector (GC) behavior for referenced object |
Free | |
---|---|---|---|---|---|
|
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 | |
|
Valid until freed explicitly. |
Across multiple threads. |
GC won't garbage collect the referenced object. |
| |
|
Valid until freed explicitly. |
Across multiple threads. |
GC can garbage collect the referenced object. |
|
We will now take a look at the reference types one by one while referring to the sample source code:
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: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.
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.
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:
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.
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.
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:
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 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.