background

Native code sometimes receives reference type parameters passed in from Java, and sometimes creates a Java reference type variable through the NewObject method. When writing Native code, be aware of the possibility that this reference representing Java data structure types will be collected by GC when used. As we know, there are four levels of references provided in Java: strong, soft, weak, and virtual:

  1. Strong applications: Strong references are declared by default in Java, such as:Object obj = new Object(); Object objects are not recycled as long as obj points to Object objectsAs long as a strong reference exists, the garbage collector will never reclaim the referenced object, even if the JVM is out of memory and throws OutofMemoryErrors instead of collecting. If you want to break the connection between a strong reference and an object, you can explicitly assign a strong reference to NULL so that the JVM can reclaim the object in time.
  2. Soft references: Soft references are used to describe objects that are not necessary but still useful. If the memory is insufficient, the system will reclaim the soft reference object. If the memory is insufficient, the system will throw an out of memory exception. This feature is often used to implement caching techniques, such as web caching, image caching, etc. After JDK1.2, using Java. Lang. Ref. SoftReference class soft references.
  3. Weak references: Weak references have a weaker reference strength than soft references, and whenever the JVM starts garbage collection, objects associated with weak references will be collected, regardless of whether memory is sufficient. After JDK1.2, using Java. Lang. Ref. WeakReference weak references.
private static void testWeakReference() { for (int i = 0; i < 10; i++) { byte\[\] buff = new byte\[1024 \* 1024\]; WeakReference<byte\[\]> sr = new WeakReference<>(buff); list.add(sr); } System.gc(); For (int I =0; i < list.size(); i++){ Object obj = ((WeakReference) list.get(i)).get(); System.out.println(obj); // The result is null because it is collected}}Copy the code
  1. Phantom reference: A virtual reference is the weakest type of reference. If an object holds only a virtual reference, it is as if it had no reference at all. It can be reclaimed at any time. It turns out that it only has a constructor and a get() method, and that its get() method simply returns null, meaning that objects can never be retrieved by virtual references, which must be used with the ReferenceQueue ReferenceQueue. Reference queues can work with soft, weak, and virtual references. When the garbage collector attempts to reclaim an object and finds that it still has a reference, it adds the reference to its associated reference queue before recycling the object. The program can determine whether the referenced object is about to be garbage collected by determining whether a reference has been added to the reference queue, so that necessary measures can be taken before the object is collected.

Native code cannot directly access its internal data interface through references, but must indirectly manipulate these reference objects by calling the JNI interface, as we’ve written in the previous series. JNI also provides reference types that correspond to Java, so we need to manage Java objects by managing these references so that they are not recycled by GC when used.

Reference profile

JNI provides three reference types:

  • Local reference
  • Global references
  • Weak global reference

Local reference

Local references are the most common type of reference. Most JNI functions create local references, such as the NewObject, FindClass, NewObjectArray functions, and so on. Local references prevent GC from retrieving referenced objects, and they cannot be passed across functions in local functions and used across threads. Local reference After the Native function returns, the referenced object is automatically reclaimed by GC or manually reclaimed by DeleteLocalRef. JNI calls cache fields and method ids. The first method uses use-time caching, which caches field ids through static variables. If a local reference created by FindClass is cached in a static variable, the local reference will be automatically freed when the function exits, and the static variable will store the freed memory address as a wild pointer, which will cause the program to crash when called again.

extern "C" JNIEXPORT jstring JNICALL Java\_com\_qingkouwei\_Demo\_errorCacheUseLocalReference( JNIEnv \*env, jobject instance) { static jmethodID mid = NULL; static jclass cls; If (CLS == NULL) {CLS = env->FindClass(" Java /lang/String"); if (CLS == NULL) {CLS ->FindClass(" Java /lang/String"); if (cls == NULL) { return NULL; } } else { LOGD("cls is not null but program will crash"); } if (mid == NULL) { mid = env->GetMethodID(cls, "<init>", "(\[C)V"); if (mid == NULL) { return NULL;  } } jcharArray charEleArr = env->NewCharArray(10);  const jchar \*j\_char = env->GetStringChars(env->NewStringUTF("LocalReference"), NULL);  env->SetCharArrayRegion(charEleArr, 0, 10, j\_char); jstring result = (jstring) env->NewObject(cls, mid, charEleArr);  env->DeleteLocalRef(charEleArr); return result; }Copy the code

Local reference manual release timing

In addition to being released automatically, local references can also be released manually using the DeleteLocalRef function, which generally exists in the following scenarios:

  • JNI local reference tables overflow when a large number of local reference objects are created. If you have the following code, take special care to release local references in a timely manner to prevent overflow.
for (int i = 0; i < len; ++i) { jstring jstr = (\*env)->GetObjectArrayElement(env, arr, i); . /\* process jstr \*/ (\*env)->DeleteLocalRef(env, jstr); }Copy the code
  • When writing utility classes, free local references in real time. When writing a tool class, it is difficult to know who will be called. Considering universality, after completing the task of the tool class, it is necessary to release the corresponding local reference in time to avoid occupying the memory space.
  • Native methods that do not need to be returned immediately release local references. If the Native method does not return, then the automatic release of local references is ineffective and must be done manually. For example, in a waiting loop, if local references are not released in time, they will soon overflow.
  • Local references are deleted as soon as they are used, not at the end of the function. For example, if a large object is created by a local reference and the object completes its task in the middle of the function, it can be released manually early rather than at the end of the function.

Managing local references

Java also provides functions to manage the life cycle of local references:

  • EnsureLocalCapacity
  • NewLocalRef
  • PushLocalFrame
  • PopLocalFrame

EnsureLocalCapacity function

The JNI specification states that the JVM must ensure that each Native method can create at least 16 local references, and experience has shown that 16 local references is sufficient for common use. However, if more local references need to be created for complex interaction calculations with the JVM’s medium objects, EnsureLocalCapacity is used to ensure that a specified number of local references can be created, returning 0 on success and less than 0 on failure, as shown in the following code example:

// Use EnsureLocalCapacity int len = 20; If (env->EnsureLocalCapacity(len) < 0) {out of memory} for (int I = 0; i < len; ++i) { jstring jstr = env->GetObjectArrayElement(arr,i); // Create enough local references to not delete here, which obviously takes up more memory}Copy the code

The references ensure that enough local references can be created, so that local references can be looping through without being deleted, but will obviously consume more memory.

PushLocalFrame pairs with PopLocalFrame

PushLocalFrame and PopLocalFrame are two matching function pairs. They can create a specified number of nested Spaces for local references, where all local references between the function pairs will remain until they are freed and you don’t have to worry about freeing each local reference. A common usage scenario is in a loop:

// Use PushLocalFrame & PopLocalFrame for (int i = 0; i < len; If (env->PushLocalFrame(len)) {if (env->PushLocalFrame(len)) {if (env->PushLocalFrame(len)) {if (env->PushLocalFrame(len)) {  i); Env ->PopLocalFrame(NULL); env->PopLocalFrame(NULL); }Copy the code

Using the PushLocalFrame & PopLocalFrame function pair, you can safely handle local references in the meantime and finally release them altogether.

Global references

A global reference, like a local reference, prevents the object it refers to from being recycled. However, it is not automatically freed when the method returns, it must be freed manually, and global references can be used across methods and threads. Global references can only be created by the NewGlobalRef function and then released manually by the DeleteGlobalRef function. Again, in the example of caching fields mentioned above, you can now cache them using global references.

extern "C" JNIEXPORT jstring JNICALL Java\_com\_qingkouwei\_Demo\_cacheWithGlobalReference(JNIEnv \*env, jobject instance) { static jclass stringClass = NULL; if (stringClass == NULL) { jclass localRefs = env->FindClass("java/lang/String"); if (localRefs == NULL) { return NULL; } stringClass = (jclass) env->NewGlobalRef(localRefs); env->DeleteLocalRef(localRefs); if (stringClass == NULL) { return NULL; } } else { LOGD("use stringClass cached"); } static jmethodID stringMid = NULL; if (stringMid == NULL) { stringMid = env->GetMethodID(stringClass, "<init>", "(Ljava/lang/String;) V"); if (stringMid == NULL) { return NULL; } } else { LOGD("use method cached"); } jstring str = env->NewStringUTF("string"); return (jstring) env->NewObject(stringClass, stringMid, str); }Copy the code

Weak global reference

A weak global reference is similar to a weak reference in Java in that the referenced object can be reclaimed by GC, and it can also be used across methods and threads. Weak references are created using the NewWeakGlobalRef method and released using the DeleteWeakGlobalRef method.

extern "C"  
 JNIEXPORT void JNICALL  
 Java\_com\_qingkouwei\_Demo\_useWeakGlobalReference(JNIEnv \*env, jobject instance) {  
     static jclass stringClass = NULL;  
     if (stringClass == NULL) {  
         jclass localRefs = env->FindClass("java/lang/String");  
         if (localRefs == NULL) {  
             return;  
         }  
         stringClass = (jclass) env->NewWeakGlobalRef(localRefs);  
         if (stringClass == NULL) {  
             return;  
         }  
     }  
     static jmethodID stringMid = NULL;  
     if (stringMid == NULL) {  
         stringMid = env->GetMethodID(stringClass, "<init>", "(Ljava/lang/String;)V");  
         if (stringMid == NULL) {  
             return;  
         }  
     }  
     jboolean isGC = env->IsSameObject(stringClass, NULL);  
     if (isGC) {  
         LOGD("weak reference has been gc");  
     } else {  
         jstring str = (jstring) env->NewObject(stringClass, stringMid,  
                                                env->NewStringUTF("jstring"));  
         LOGD("str is %s", env->GetStringUTFChars(str, NULL));  
     }  
 }
Copy the code

When using a weak reference, check to see if the class object referenced by the weak reference has been collected by the GC. This is checked by the isSameObject method. The isSameObject method can be used to compare whether two reference types are the same or whether a reference is NULL. It is also possible to use isSameObject to compare whether the object referenced by a weak global reference was GC, returning JNI_TRUE to indicate that it was GC, and JNI_FALSE to indicate that it was not GC.

Env ->IsSameObject(obj1, obj2) Env ->IsSameObject(wobj, NULL) // Compares whether local or global references are NULLCopy the code

Manage references properly

Summarize some knowledge points about reference management, which can reduce memory usage and avoid memory waste caused by object being referenced and cannot be released. In general, there are two cases for Native code:

  • Code that directly implements Native functions declared by the Java layer
  • Utility functions for use in any scenario

For Native functions that implement Java layer declarations directly, do not accumulate global and weak global references because they are not automatically released when the function returns. For Native functions of tool classes, due to its uncertain call occasions and times, it is necessary to be careful of various reference types to avoid memory overflow caused by accumulation, such as the following rules:

  • Native utility functions that return base types cannot result in accumulation of global, weak, and local references.
  • Native utility functions that return reference types cannot cause any accumulation of global, weak, or local references in addition to the references to be returned. At the same time, for Native functions of utility classes, it is also efficient to use caching technology to save some global references, as described in the article on caching fields and method IDS for Android JNI calls. Also, in a utility class, if a reference type is returned, it is best to specify the type of reference being returned, as shown in the following code:
while (JNI\_TRUE) { jstring infoString = GetInfoString(info); . /\* process infoString \*/ ??? /\* we need to call DeleteLocalRef, DeleteGlobalRef, or DeleteWeakGlobalRef depending on the type of reference returned by GetInfoString. \*/ }Copy the code

Because it is not clear what type of reference is being returned, it is not known which method is called to remove the reference type. JNI provides the NewLocalRef function to ensure that the utility class function always returns a local reference type, as follows:

static jstring cachedString = NULL;  
 if (cachedString == NULL) {  
       /\* create cachedString for the first time \*/  
       jstring cachedStringLocal = ... ;  
       /\* cache the result in a global reference \*/  
       cachedString =(\*env)->NewGlobalRef(env, cachedStringLocal);  
 }  
return (\*env)->NewLocalRef(env, cachedString);
Copy the code

As mentioned earlier, static caches cannot cache a local reference, and cachedString is a cached global reference, but NewLocalRef ensures that a local reference is returned, which is automatically freed after reuse. The best way to manage references is to use the PushLocalFrame and PopLocalFrame pairs. Local references between these pairs are automatically taken over by PushLocalFrame and PopLocalFrame.

conclusion

This article introduces reference types and reference management for the JNI layer. The JNI layer mainly provides local and global references for strong applications of standard Java and global weak references for weak references of standard Java. For global references, always remember to Delete when you run out; If there are too many local references in a method, try EnsureLocalCapacity. References must be carefully managed to avoid memory leaks and wild, null pointer problems.