注:本文中笔者所用源代码版本为Android5.0.1
综述
在Java虚拟的运行时数据分区中,本地方法栈用来保存native方法调用中产生的引用,虚拟机可以用之来做可达性分析GC Roots。
如果本地方法栈中的引用一直存活,且保持一定数量,则与之相关的对象将无法被回收。此时就可能存在内存泄露的风险。
Java虚拟机不允许无限制地创建本地引用,它为本地引用的数量设置了上限,一旦超过这个上限就会报出JNI ERROR XXX table overflow (max = XX)异常。
Java虚拟机运行时数据区
JVM中对内存进行回收时,需要判断对象是否仍在使用中,可以通过GC Roots Tracing辨别。通过一系列名为“GCRoots”的对象作为起始点,从这个节点向下搜索,搜索走过的路径称为ReferenceChain,当一个对象到GCRoots没有任何ReferenceChain相连时,(图论:这个对象不可到达),则证明这个对象不可用。
可以作为GC Root 引用点的是:
- JavaStack中的引用的对象。
- 方法区中静态引用指向的对象。
- 方法区中常量引用指向的对象。
- Native方法中JNI引用的对象。
本地方法中的JNI引用
JNI提供了一些实例和数组类型(jobject、jclass、jstring、jarray等)作为不透明的引用供本地代码使用。本地代码永远不会直接操作引用指向的VM内部的数据内容。要进行这些操作,必须通过使用JNI操作一个不透明的引用来间接操作数据内容。
- JNI支持三种引用:局部引用、全局引用、弱全局引用。
- 局部应用和全局引用有不同的生命周期。当本地方法返回时,局部引用会被自动释放。而全局引用和弱引用必须手动释放。
- 局部引用或全局引用会阻止GC回收它们所引用的对象,而弱引用则不会。
- 不是所有的引用可以被用在所有的场合。例如:一个本地方法创建一个局部引用并返回,再对这个局部引用进行访问是非法的。
局部引用
局部引用只有在创建它的本地方法返回前有效。本地方法返回后,局部引用会被自动释放。
1:static jclass strClass = (*env).FindClass("java/lang/String");
2:(*env)->DeleteLocalRef(env, strClass);
释放一个局部引用有两种方式,一个是本地方法执行完毕后VM自动释放,另外一个是程序员通过DeleteLocalRef手动释放。因为局部引用会阻止它所引用的对象被 GC回收,所以建议使用后者。
全局引用
全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用也会阻止它所引用的对象被 GC 回收。 与局部引用可以被大多数 JNI函数创建不同,全局引用只能使用一个 JNI函数创建:NewGlobalRef。
1: /* Create a global reference */
2: stringClass = (*env)->NewGlobalRef(env, localRefCls);
3: /* The local reference is no longer useful */
4: (*env)->DeleteGlobalRef(env, localRefCls);
弱引用
弱引用使用NewGlobalWeakRef创建,使用DeleteGlobalWeakRef释放。与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的 VM内部的对象。
本地引用管理
初始化虚拟机的时候会为局部引用、全局应用、弱引用都创建一个IndirectReferenceTable对象,并初始化它们的引用数上限。
Jni调用函数中每增加一个引用都会调用IndirectReferenceTable::Add(uint32_t cookie, mirror::Object* obj)方法将该引用添加到一个引用表中。在添加之前需要检验当前引用表中的引用数,如果该引用数已经达到上限则报出异常:
art/runtime/indirect_reference_table.cc
IndirectReferenceTable(size_t initialCount, size_t maxCount, IndirectRefKind kind);
max_entries_(maxCount){……}
……
IndirectRef IndirectReferenceTable::Add(uint32_t cookie, mirror::Object* obj)
{
……
if (topIndex == max_entries_) {
LOG(FATAL) << "JNI ERROR (app bug): " << kind_ << " table overflow "
<< "(max=" << max_entries_ << ")\n"
<< MutatorLockedDumpable<IndirectReferenceTable>(*this);
}
……
}
……
初始化引用数上限
/art/runtime/Jni_internal.cc
……
// 局部引用的初始值和最大值
static const size_t kLocalsInitial = 64; // Arbitrary.
static const size_t kLocalsMax = 512; // Arbitrary sanity check.
// 全局引用的初始值和最大值
static size_t gGlobalsInitial = 512; // Arbitrary.
static size_t gGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.)
// 弱引用的初始值和最大值
static const size_t kWeakGlobalsInitial = 16; // Arbitrary.
static const size_t kWeakGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.)
……
// 初始化局部引用表
IndirectReferenceTable& locals = reinterpret_cast<JNIEnvExt*>(env)->locals;
……
locals(kLocalsInitial, kLocalsMax, kLocal)
……
// 初始化全局引用表
IndirectReferenceTable& globals = vm->globals;
……
globals(gGlobalsInitial, gGlobalsMax, kGlobal)
……
// 初始化弱引用表
IndirectReferenceTable weak_globals_
……
weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal)
……