上面这张图你们都应该很熟了,下面只讲下和JNI有关的部分html
记录正在执行的虚拟机字节码指令的地址(若是正在执行的是本地方法则为空)。java
本地方法栈与 Java 虚拟机栈相似,它们之间的区别只不过是本地方法栈为本地方法服务。 本地方法通常是用其它语言(C、C++ 或汇编语言等)编写的,而且被编译为基于本机硬件和操做系统的程序,对待这些方法须要特别处理。 android
全部对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。 堆不须要连续内存,而且能够动态增长其内存,增长失败会抛出 OutOfMemoryError 异常。git
能够经过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。github
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的主要做用以下:bash
在Java代码中,Java对象被存放在JVM的Java Heap,由垃圾回收器(Garbage Collector,即GC)自动回收就能够。多线程
在Native代码中,内存是从Native Memory中分配的,须要根据Native编程规范去操做内存。如:C/C++使用malloc()/new分配内存,须要手动使用free()/delete回收内存。app
然而,JNI和上面二者又有些区别。 JNI提供了与Java相对应的引用类型(如:jobject、jstring、jclass、jarray、jintArray等),以便Native代码能够经过JNI函数访问到Java对象。引用所指向的Java对象一般就是存放在Java Heap,而Native代码持有的引用是存放在Native Memory中。jvm
举个例子,以下代码:
jstring jstr = env->NewStringUTF("Hello World!");
复制代码
NewStringUTF()
用于构造一个String对象,该对象存放在Java Heap中,同时返回了一个jstring类型的引用。开发人员都应该遇到过OOM(Out of Memory)异常,在JNI开发中,该异常可能发生在Java Heap中,也可能发生在Native Memory中。
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: native memory exhausted
复制代码
Java Heap 中出现 Out of Memory异常的缘由有两种:
1)程序过于庞大,导致过多 Java 对象的同时存在;
2)程序编写的错误致使 Java Heap 内存泄漏。
复制代码
Native Memory中出现 Out of Memory异常的缘由:
1)程序申请过多资源,系统未能知足,好比说大量线程资源;
2)程序编写的错误致使Native Memory内存泄漏。
复制代码
JNI引用有三种:Local Reference
、Global Reference
、Weak Global Reference
。下面分别来介绍一下这三种引用内存分配和管理。
Local Reference
只在Native Method执行时存在,只在建立它的线程有效,不能跨线程使用。它的生命期是在Native Method的执行期开始建立(从Java代码切换到Native代码环境时,或者在Native Method执行时调用JNI函数时),在Native Method执行完毕切换回Java代码时,全部Local Reference被删除(GC会回收其内存),生命期结束(调用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!");
复制代码
Local Reference Table
的内存不大,所能存放的Local Reference
数量也是有限的(在Android中默认最大容量是512个),使用不当就会引发溢出异常Local Reference
并非Native里面的局部变量,局部变量存放在堆栈中,其引用存放在Local Reference Table
中。在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会本身回收。
Global Reference
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);
复制代码
使用 Global reference
时,当 native code 再也不须要访问Global reference
时,应当调用 JNI 函数 DeleteGlobalRef()
删除 Global reference
和它引用的 Java 对象。不然Global Reference
引用的 Java 对象将永远停留在 Java Heap 中,从而致使 Java Heap 的内存泄漏。
Weak Global Reference
用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,只是个整数,不存在被释放与否的问题。
注意Local Reference的生命周期,若是在Native中须要长时间持有一个Java对象,就不能使用将jobject存储在Native,不然在下次使用的时候,即便同一个线程调用,也将会没法使用。下面是错误的作法:
jstring global;
extern "C" JNIEXPORT jstring JNICALL
Java_org_hik_libyuv_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
jstring local = env->NewStringUTF(hello.c_str());
global = local;
return local;
}
复制代码
正确的作法是使用Global Reference,以下:
jstring global;
extern "C" JNIEXPORT jstring JNICALL
Java_org_hik_libyuv_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
jstring local = env->NewStringUTF(hello.c_str());
global = static_cast<jstring>(env->NewGlobalRef(global));
return local;
}
复制代码
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环境一直被占用。