stay JNI/NDK Development Guide (x) -- JNI local reference, global reference and weak global reference This article introduces in detail the use methods, differences, application scenarios and development considerations of the three references in JNI. Because they are all theories, they may not be impressed enough after reading, which is an error prone place in development. Therefore, this article uses an example to illustrate the problems caused by improper use of quotation, so as to arouse everyone's attention to this knowledge point.
First, create a Android project. Put a text box and a button on the main interface. The text box is used to receive the number of N to create the local reference. After clicking the button, it will get the number of the text box, then call the native method to create an array of character strings of length N in the local code, then return to the Java layer and output to the console.
The interface is as follows:
activity_ main. The XML is as follows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:padding="5dip" > <EditText android:id="@+id/str_count" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:inputType="numberDecimal" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/str_count" android:onClick="onTestLocalRefOverflow" android:text="Local reference table overflow test" /> </LinearLayout>
Declare the native method and initialize the View in MainActivity
package com.example.jni; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; public class MainActivity extends Activity { // Return an array of strings with the same count samples and identify them with numbers, such as sample1, sample2 public native String[] getStrings(int count, String sample); EditText mEditText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mEditText = (EditText) findViewById(R.id.str_count); } public void onTestLocalRefOverflow(View view) { String[] strings = getStrings(Integer.parseInt(mEditText.getText().toString()),"I Love You %d Year!!!"); for (String string : strings) { System.out.println(string); } } static { System.loadLibrary("local_ref_overflow_test"); } }
The code in Java is relatively simple. MainActivity declares a native method getStrings, which is used to call the local function. The onTestLocalRefOverflow method is the click event of the button in the main interface. After calling the button, the getStrings method is invoked, the number of strings and the content of the word string are passed in, and then the N array of the same string length is returned.
Next, create a jni directory under the project, And create Android mk ,Application.mk and local_ref_overflow_test.c file, where Android MK is a description file for the NDK compilation system to automatically compile and package C/C + + source code. Application.mk is used to describe some parameter options during NDK compilation, such as C/C + + precompiled macros, CPU architecture, etc. (detailed in the following meeting)_ ref_ overflow_ test. C is the C code that implements the getStrings local method in MainActivity.
Android. The contents of the MK file are as follows:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #Clear environment variables LOCAL_MODULE := local_ref_overflow_test #so file name, without adding lib prefix and so suffix LOCAL_SRC_FILES := local_ref_overflow_test.c #C source file LOCAL_LDLIBS := -llog #Link log module include $(BUILD_SHARED_LIBRARY) #Compile source files into shared libraries
Application. The contents of the MK file are as follows:
APP_ABI := armeabi armeabi-v7a #Specifies the type of CPU schema to compile
local_ref_overflow_test.c the contents of the document are as follows:
#include <jni.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <android/log.h> #define LOG_TAG "MainActivity" #define LOG_I(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__) #define LOG_E(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #ifdef __cplusplus extern "C" { #endif jobjectArray getStrings(JNIEnv *env, jobject obj, jint count, jstring sample) { jobjectArray str_array = NULL; jclass cls_string = NULL; jmethodID mid_string_init; jobject obj_str = NULL; const char *c_str_sample = NULL; char buff[256]; int i; // Ensure that at least 3 local references (str_array, cls_string, obj_str) can be created if ((*env)->EnsureLocalCapacity(env, 3) != JNI_OK) { return NULL; } c_str_sample = (*env)->GetStringUTFChars(env, sample, NULL); if (c_str_sample == NULL) { return NULL; } cls_string = (*env)->FindClass(env, "java/lang/String"); if (cls_string == NULL) { return NULL; } // Get the construction method of String mid_string_init = (*env)->GetMethodID(env, cls_string, "<init>", "()V"); if (mid_string_init == NULL) { (*env)->DeleteLocalRef(env,cls_string); return NULL; } obj_str = (*env)->NewObject(env, cls_string, mid_string_init); if (obj_str == NULL) { (*env)->DeleteLocalRef(env,cls_string); return NULL; } // Create an array of strings str_array = (*env)->NewObjectArray(env, count, cls_string, obj_str); if (str_array == NULL) { (*env)->DeleteLocalRef(env,cls_string); (*env)->DeleteLocalRef(env,obj_str); return NULL; } // Assign a value to each element in the array for (i = 0; i < count; ++i) { memset(buff, 0, sizeof(buff)); // Initial buffer sprintf(buff, c_str_sample,i); jstring newStr = (*env)->NewStringUTF(env, buff); (*env)->SetObjectArrayElement(env, str_array, i, newStr); } // Free up memory occupied by template string (*env)->ReleaseStringUTFChars(env, sample, c_str_sample); // Free up resources occupied by local references (*env)->DeleteLocalRef(env, cls_string); (*env)->DeleteLocalRef(env, obj_str); return str_array; } const JNINativeMethod g_methods[] = { {"getStrings", "(ILjava/lang/String;)[Ljava/lang/String;", (void*)getStrings} }; static jclass g_cls_MainActivity = NULL; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { LOG_I("JNI_OnLoad method call begin"); JNIEnv* env = NULL; jclass cls = NULL; if((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // Find the local method Class reference to load cls = (*env)->FindClass(env, "com/example/jni/MainActivity"); if(cls == NULL) { return JNI_ERR; } // Cache the reference of class into global variables g_cls_MainActivity = (*env)->NewWeakGlobalRef(env, cls); (*env)->DeleteLocalRef(env, cls); // Manually deleting local references is a good habit // Bind the native method in java to the local function (*env)->RegisterNatives(env, g_cls_MainActivity, g_methods, sizeof(g_methods) / sizeof(g_methods[0])); LOG_I("JNI_OnLoad method call end"); return JNI_VERSION_1_6; } JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { LOG_I("JNI_OnUnload method call begin"); JNIEnv *env = NULL; if((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { return; } (*env)->UnregisterNatives(env, g_cls_MainActivity); // Unregister when so is uninstalled (*env)->DeleteWeakGlobalRef(env, g_cls_MainActivity); } #ifdef __cplusplus } #endif
If you read from previous articles, there are several functions in the above local code that you may not have seen before. The following is a brief description, which will be described in detail in an article later. Including JNI_OnLoad is to call system. Net at the Java layer The loadlibrary method is the callback function when loading the shared library to the virtual machine. It is suitable for some initialization here. JNI_ The onunload function is called back by the virtual machine when the shared library is unloaded. It is suitable for resource release and memory recovery. The RegisterNatives function in line 104 is used to bind the local function to the Java native method. In this example, instead of generating the header file declaration with the javah command in the original way, the getStrings native method in Java is bound with the local function getStrings with the RegisterNatives function. It can also realize the function of function search, and it is more efficient. JNI nativemethod is a data structure used to describe a method name, function signature and function pointer information, and to bind the mapping relationship between local functions and Java native methods. As follows:
typedef struct { char *name; // Function name char *signature; // Function signature void *fnPtr; // Function pointer } JNINativeMethod;
Note: void *fnPtr is the function parameter pointed to by the function pointer. Note that the first parameter of the local function must be JNIEnv *, the second parameter if it is an instance method is jobject, the static method is jclass, and the latter is the parameter of the native method in Java. For example, in the above example, the native method getStrings declared in MainActivity: public native String[] getStrings(int count, String sample); Corresponding to the local function JNIEnv * Env, jobobject obj, jint count, jstring sample.
I won't introduce the code of getStrings in detail. It is the function of creating a string array, which has been described many times in previous articles. Now read the implementation of this function carefully to see if you can find out which place will cause local reference table overflow. If you run the program now and enter a value greater than 501 in the text box, you will see the crash due to local reference table overflow. As shown in the figure below:
At this time, you may think of using the ensureleocalcapacity or PushLocalFrame/PopLocalFrame interface learned in the previous article to expand the number of local references. For example, change line 25 to if (* Env) - > ensure local capacity (Env, count+3)= JNI_ OK) to ensure that count+3 references can be created in the function (here 3 refers to str_array, cls_string and obj_str). Unfortunately, insurelocal capacity will try to apply for a specified number of local references, but it may not succeed, because local references are created in the stack. If the memory space applied by this order of magnitude exceeds the maximum memory space of the stack, memory overflow will be caused. The results are shown in the figure below:
Therefore, in a local method, if a large number of local references are used and not released in time, it may cause program crash at any time. In the above example, at line 63, each traversal will create a new string and return a local reference to the string. After line 64 is used up, it will not be managed. As a result, when a large array is created, the local reference table will be filled, resulting in reference table overflow. After testing, the default maximum capacity of local reference tables in Android is 512. This is implemented by virtual machine. There should be no way to modify this number in the program. Seeing this, I think you should know how to fix this problem. Yes, directly after the 64 row set the string to the array element, call DeleteLocalRef to delete it. The modified code is as follows:
// Assign a value to each element in the array for (i = 0; i < count; ++i) { memset(buff, 0, sizeof(buff)); // Initial buffer sprintf(buff, c_str_sample,i); jstring newStr = (*env)->NewStringUTF(env, buff); (*env)->SetObjectArrayElement(env, str_array, i, newStr); (*env)->DeleteLocalRef(env,newStr); // Warning: if the local reference is not released manually here, it is likely to overflow the local reference table }
After modification, there is no problem with how large the string array you create. Of course, you can't exceed the size of physical memory! Because all the memory allocated by the created objects in Java is stored in heap space. Next, create a string array of 500000 lengths to verify, as shown in the following figure:
Demo GIT download address: git@code.csdn.net:xyang81/jnilocalrefoverflowtest.git