上面这张图大家都应该很熟了,下面只讲下和JNI有关的部分
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。 堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
java -Xmx1024m -Xms1024m //-Xmx1024m:设置JVM最大可用内存为1024M。 //-Xms1024m:设置JVM初始内存为1024m。此值可与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。 复制代码
在Android系统对于每个应用都有内存使用的限制,机器的内存限制,在/system/build.prop文件中配置的。可以在manifest文件application节点加入 android:largeHeap="true"
来让Dalvik/ART虚拟机分配更大的堆内存空间
也称为C-Heap,供Java Runtime进程使用的,没有相应的参数来控制其大小,其大小依赖于操作系统进程的最大值。 Java应用程序都是在Java Runtime Environment(JRE)中运行,而Runtime本身就是由Native语言(如:C/C++)编写程序。Native Memory就是操作系统分配给Runtime进程的可用内存,它与Heap Memory不同,Java Heap 是Java应用程序的内存。。Native Memory的主要作用如下:
在Java代码中,Java对象被存放在JVM的Java Heap,由垃圾回收器(Garbage Collector,即GC)自动回收就可以。
在Native代码中,内存是从Native Memory中分配的,需要根据Native编程规范去操作内存。如:C/C++使用malloc()/new分配内存,需要手动使用free()/delete回收内存。
然而,JNI和上面两者又有些区别。 JNI提供了与Java相对应的引用类型(如:jobject、jstring、jclass、jarray、jintArray等),以便Native代码可以通过JNI函数访问到Java对象。引用所指向的Java对象通常就是存放在Java Heap,而Native代码持有的引用是存放在Native Memory中。
举个例子,如下代码:
jstring jstr = env->NewStringUTF("Hello World!"); 复制代码
开发人员都应该遇到过OOM(Out of Memory)异常,在JNI开发中,该异常可能发生在Java Heap中,也可能发生在Native Memory中。 java.lang.OutOfMemoryError: Java heap space java.lang.OutOfMemoryError: native memory exhausted
1)程序过于庞大,致使过多 Java 对象的同时存在; 2)程序编写的错误导致 Java Heap 内存泄漏。 复制代码
1)程序申请过多资源,系统未能满足,比如说大量线程资源; 2)程序编写的错误导致Native Memory内存泄漏。 复制代码
JNI引用有三种:Local Reference、Global Reference、Weak Global Reference。下面分别来介绍一下这三种引用内存分配和管理。
只在Native Method执行时存在。它的生命期是在Native Method的执行期开始创建(从Java代码切换到Native代码环境时,或者在Native Method执行时调用JNI函数时),在Native Method执行完毕切换回Java代码时,所有Local Reference被删除,生命期结束(调用DeleteLocalRef可以提前结束其生命期)。
实际上,每当线程从Java环境切换到Native代码环境时,JVM 会分配一块内存用于创建一个Local Reference Table,这个Table用来存放本次Native Method 执行中创建的所有Local Reference。每当在 Native代码中引用到一个Java对象时,JVM 就会在这个Table中创建一个Local Reference。比如,我们调用 NewStringUTF() 在 Java Heap 中创建一个 String 对象后,在 Local Reference Table 中就会相应新增一个 Local Reference。
Local Reference 表、Local Reference 和 Java 对象的关系
接下来举个简单例子说明一下:
jstring jstr = env->NewStringUTF("Hello World!"); 复制代码
在Native Method结束时,JVM会自动释放Local Reference,但 Local Reference Table
是有大小限制的,在开发中应该及时使用DeleteLocalRef()删除不必要的Local Reference,不然可能会出现溢出错误:
JNI ERROR (app bug): local reference table overflow (max=512) 复制代码
在C/C++中实例化的JNI对象,如果不返回java,必须用release掉或delete,否则内存泄露。包括NewStringUTF,NewObject。对于一般的基本数据类型(如:jint,jdouble等),是没必要调用该函数删除掉的。如果返回java不必delete,java会自己回收。
Local Reference是在Native Method执行的时候出现的,而Global Reference是通过JNI函数NewGlobalRef()和DeleteGlobalRef()来创建和删除的。 Global Reference具有全局性,可以在多个Native Method调用过程和多线程中使用,在主动调用DeleteGlobalRef之前,它是一直有效的(GC不会回收其内存)。
/** * 创建obj参数所引用对象的新全局引用。obj参数既可以是全局引用,也可以是局部引用。全局引用通过调用DeleteGlobalRef()来显式撤消。 * @param obj 全局或局部引用。 * @return 返回全局引用。如果系统内存不足则返回 NULL。 */ jobject NewGlobalRef(jobject obj); /** * 删除globalRef所指向的全局引用 * @param globalRef 全局引用 */ void DeleteGlobalRef(jobject globalRef); 复制代码
用NewWeakGlobalRef()和DeleteWeakGlobalRef()进行创建和删除,它与Global Reference的区别在于该类型的引用随时都可能被GC回收。
对于Weak Global Reference而言,可以通过isSameObject()将其与NULL比较,看看是否已经被回收了。如果返回JNI_TRUE,则表示已经被回收了,需要重新初始化弱全局引用。 Weak Global Reference的回收时机是不确定的,有可能在前一行代码判断它是可用的,后一行代码就被GC回收掉了。为了避免这事事情发生,JNI官方给出了正确的做法,通过NewLocalRef()获取Weak Global Reference,避免被GC回收。
很多人会误将 JNI 中的 Local Reference 理解为 Native Code 的局部变量。这是错误的。
Native Code 的局部变量和 Local Reference 是完全不同的,区别可以总结为:
⑴局部变量存储在线程堆栈中,而 Local Reference 存储在 Local Ref 表中。
⑵局部变量在函数退栈后被删除,而 Local Reference 在调用 DeleteLocalRef() 后才会从 Local Ref 表中删除,并且失效,或者在整个 Native Method 执行结束后被删除。
⑶可以在代码中直接访问局部变量,而 Local Reference 的内容无法在代码中直接访问,必须通过 JNI function 间接访问。JNI function 实现了对 Local Reference 的间接访问,JNI function 的内部实现依赖于具体 JVM。
extern "C" JNIEXPORT jstring JNICALL Java_com_test_application_MainActivity_init(JNIEnv *env, jobject instance, jstring data, jbyteArray array) { int len = env->GetArrayLength(array); const char *utfChars = env->GetStringUTFChars(data, 0); jbyte *arrayElements = env->GetByteArrayElements(array, NULL); jstring pJstring = env->NewStringUTF(utfChars); jbyteArray jpicArray = env->NewByteArray(len); env->SetByteArrayRegion(jpicArray, 0, len, arrayElements); // TODO env->DeleteLocalRef(pJstring); env->DeleteLocalRef(jpicArray); env->ReleaseStringUTFChars(data, utfChars); env->ReleaseByteArrayElements(array, arrayElements, 0); std::string hello = "Hello from C++"; jstring result = env->NewStringUTF(hello.c_str()); return result; } 复制代码
其它的还有:
jclass ref= (env)->FindClass("java/lang/String"); env->DeleteLocalRef(ref); 复制代码
因为根据 jni.h
里的定义:
typedef jobject jclass; 复制代码
jclass也是jobject。而jmethodID/jfielID和jobject没有继承关系,它们不是object,只是个整数,不存在被释放与否的问题。
JNIEnv和jobject对象都不能跨线程使用。 对于jobject,解决办法是
a、m_obj = env->NewGlobalRef(obj);//创建一个全局变量 b、jobject obj = env->AllocObject(m_cls);//在每个线程中都生成一个对象 复制代码
对于JNIEnv,解决办法是在每个线程中都重新生成一个env
JavaVM *gJavaVM;//声明全局变量 (*env)->GetJavaVM(env, &gJavaVM);//在JNI方法的中赋值 JNIEnv *env;//在其它线程中获取当前线程的env m_jvm->AttachCurrentThread((void **)&env, NULL); 复制代码
当在一个线程里面调用AttachCurrentThread后,如果不需要用的时候一定要DetachCurrentThread,否则线程无法正常退出,导致JNI环境一直被占用。