NDK开发中JNI ERROR XXX table overflow (max = XX)异常

注:本文中笔者所用源代码版本为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)
      ……
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值