Android JNI介绍(七)- 引用的管理

4,399 阅读25分钟

本系列文章列表:

Android JNI介绍(一)- 第一个Android JNI工程

Android JNI介绍(二)- 第一个JNI工程的详细分析

Android JNI介绍(三)- Java和Native的互相调用

Android JNI介绍(四)- 异常的处理

Android JNI介绍(五)- 函数的注册

Android JNI介绍(六)- 依赖其他库

Android JNI介绍(七)- 引用的管理

Android JNI介绍(八)- CMakeLists的使用


在前面的文章中,我们已经了解了JNI的工程结构、调用流程、异常处理等知识,本文将介绍JNI中的引用管理。

一、引用类型

JNI中封装了以下三种引用类型:

  • LocalReference

    局部引用,引用表的持有者是JNIEnv,在函数执行完时会自动释放,但是在函数执行过程中创建过多就会导致内存溢出或引用表溢出。

  • GlobalReference

    全局强引用,引用表的持有者是JVM,引用对象不会被gc,手动创建,手动释放,如果创建过多并且不释放会导致内存溢出或引用表溢出。

  • WeakGlobalReference

    全局弱引用,引用表的持有者是JVM,引用对象可能会被gc,手动创建,手动释放,如果创建过多并且不释放会导致内存溢出或引用表溢出。

二、各个引用的详细介绍

1. 引用表的介绍

引用表的数据类型为IndirectReferenceTable,定义在IndirectReferenceTable.h中,其实现是MemMap,是一块连续内存,可以理解为数组,通过下标进行标识当前数组内的数据量。对于Add操作,直接追加即可,在空间不够时通过Resize扩张,Resize的实现类似于Java中的ArrayList扩张的实现:申请新的内存,再进行拷贝。对于Remove操作,如果Remove的是顶部元素,可以在释放对象并置空,直接修改下标,但是对于非顶部元素的移除,由于这个对象的内存是连续的,而不是链表,不能直接释放那块内存,因此,IndirectReferenceTable在进行删除操作可能仍会占用一个内存区域,代码里称之为Hole,在其进行Add时,又会对这个Hole进行填充,尽可能地充分利用内存。

indirect_reference_table.cc中,Add函数的实现如下

IndirectRef IndirectReferenceTable::Add(IRTSegmentState previous_state,
                                        ObjPtr<mirror::Object> obj,
                                        std::string* error_msg) {
  if (kDebugIRT) {
    LOG(INFO) << "+++ Add: previous_state=" << previous_state.top_index
              << " top_index=" << segment_state_.top_index
              << " last_known_prev_top_index=" << last_known_previous_state_.top_index
              << " holes=" << current_num_holes_;
  }

  size_t top_index = segment_state_.top_index;

  CHECK(obj != nullptr);
  VerifyObject(obj);
  DCHECK(table_ != nullptr);

  // 当前表已满
  if (top_index == max_entries_) {
    // 对于不可Resize的情况,报错
    if (resizable_ == ResizableCapacity::kNo) {
      std::ostringstream oss;
      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
          << "(max=" << max_entries_ << ")"
          << MutatorLockedDumpable<IndirectReferenceTable>(*this);
      *error_msg = oss.str();
      return nullptr;
    }

    // 如果当前容量大于最大值的一半,x2就会过大,直接报错
    // Try to double space.
    if (std::numeric_limits<size_t>::max() / 2 < max_entries_) {
      std::ostringstream oss;
      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
          << "(max=" << max_entries_ << ")" << std::endl
          << MutatorLockedDumpable<IndirectReferenceTable>(*this)
          << " Resizing failed: exceeds size_t";
      *error_msg = oss.str();
      return nullptr;
    }
    
    // 尝试扩大一倍
    std::string inner_error_msg;
    if (!Resize(max_entries_ * 2, &inner_error_msg)) {
      std::ostringstream oss;
      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
          << "(max=" << max_entries_ << ")" << std::endl
          << MutatorLockedDumpable<IndirectReferenceTable>(*this)
          << " Resizing failed: " << inner_error_msg;
      *error_msg = oss.str();
      return nullptr;
    }
  }

  RecoverHoles(previous_state);
  CheckHoleCount(table_, current_num_holes_, previous_state, segment_state_);

  // We know there's enough room in the table.  Now we just need to find
  // the right spot.  If there's a hole, find it and fill it; otherwise,
  // add to the end of the list.
  IndirectRef result;
  size_t index;
  if (current_num_holes_ > 0) {
    DCHECK_GT(top_index, 1U);
    // Find the first hole; likely to be near the end of the list.
    IrtEntry* p_scan = &table_[top_index - 1];
    DCHECK(!p_scan->GetReference()->IsNull());
    --p_scan;
    while (!p_scan->GetReference()->IsNull()) {
      DCHECK_GE(p_scan, table_ + previous_state.top_index);
      --p_scan;
    }
    index = p_scan - table_;
    current_num_holes_--;
  } else {
    // Add to the end.
    index = top_index++;
    segment_state_.top_index = top_index;
  }
  table_[index].Add(obj);
  result = ToIndirectRef(index);
  if (kDebugIRT) {
    LOG(INFO) << "+++ added at " << ExtractIndex(result) << " top=" << segment_state_.top_index
              << " holes=" << current_num_holes_;
  }

  DCHECK(result != nullptr);
  return result;
}

Remove的实现如下

// Removes an object. We extract the table offset bits from "iref"
// and zap the corresponding entry, leaving a hole if it's not at the top.
// If the entry is not between the current top index and the bottom index
// specified by the cookie, we don't remove anything. This is the behavior
// required by JNI's DeleteLocalRef function.
// This method is not called when a local frame is popped; this is only used
// for explicit single removals.
// Returns "false" if nothing was removed.
bool IndirectReferenceTable::Remove(IRTSegmentState previous_state, IndirectRef iref) {
  ...
  if (idx == top_index - 1) {
    // Top-most entry.  Scan up and consume holes.

    if (!CheckEntry("remove", iref, idx)) {
      return false;
    }

    *table_[idx].GetReference() = GcRoot<mirror::Object>(nullptr);
    if (current_num_holes_ != 0) {
      uint32_t collapse_top_index = top_index;
      while (--collapse_top_index > bottom_index && current_num_holes_ != 0) {
        if (kDebugIRT) {
          ScopedObjectAccess soa(Thread::Current());
          LOG(INFO) << "+++ checking for hole at " << collapse_top_index - 1
                    << " (previous_state=" << bottom_index << ") val="
                    << table_[collapse_top_index - 1].GetReference()->Read<kWithoutReadBarrier>();
        }
        if (!table_[collapse_top_index - 1].GetReference()->IsNull()) {
          break;
        }
        if (kDebugIRT) {
          LOG(INFO) << "+++ ate hole at " << (collapse_top_index - 1);
        }
        current_num_holes_--;
      }
      segment_state_.top_index = collapse_top_index;

      CheckHoleCount(table_, current_num_holes_, previous_state, segment_state_);
    } else {
      segment_state_.top_index = top_index - 1;
      if (kDebugIRT) {
        LOG(INFO) << "+++ ate last entry " << top_index - 1;
      }
    }
  } else {
    // Not the top-most entry.  This creates a hole.  We null out the entry to prevent somebody
    // from deleting it twice and screwing up the hole count.
    if (table_[idx].GetReference()->IsNull()) {
      LOG(INFO) << "--- WEIRD: removing null entry " << idx;
      return false;
    }
    if (!CheckEntry("remove", iref, idx)) {
      return false;
    }

    *table_[idx].GetReference() = GcRoot<mirror::Object>(nullptr);
    current_num_holes_++;
    CheckHoleCount(table_, current_num_holes_, previous_state, segment_state_);
    if (kDebugIRT) {
      LOG(INFO) << "+++ left hole at " << idx << ", holes=" << current_num_holes_;
    }
  }

  return true;
}

2. LocalReference

局部引用,我们调用的FindClassGetObjectClassNewStringUTFNewObjectArrayNewByteArrayCallObjectMethod等函数回传的都是局部引用。在没有循环处理的情况下,我们可以让其自动释放,但是在循环次数较多的循环中,需要手动释放。如果局部引用表溢出,就会出现异常。需要注意的是,局部引用不能保存在全局变量中使用,否则会报JNI DETECTED ERROR IN APPLICATION: use of deleted local reference错误提示。

  • 示例操作

    正确示例

    extern "C" JNIEXPORT void
    JNICALL
    Java_com_wsy_jnidemo_test_ReferenceTest_createLocalRef(
            JNIEnv *env,
            jclass,jint count) {
        for (int i = 0; i < count; ++i) {
            jobject localRef = env->NewByteArray(1);
            ... 
            env->DeleteLocalRef(localRef);
        }
    }
    

    错误示例

    extern "C" JNIEXPORT void
    JNICALL
    Java_com_wsy_jnidemo_test_ReferenceTest_createLocalRef(
            JNIEnv *env,
            jclass,jint count) {
        for (int i = 0; i < count; ++i) {
            env->NewByteArray(1);
        }
    }
    

    对于错误示例,当我们分10000次,每次创建10000个LocalReference的方式进行调用,并不会crash

        for (int i = 0; i < 10000; i++) {
           ReferenceTest.createLocalRef(10000);
        }
    

    而当我们只调用一次,创建100000000个LocalReference的方式进行调用,就会crash

        ReferenceTest.createLocalRef(100000000);
    

    crash日志类似如下,提示local reference table overflow

    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG: Abort message: 'JNI ERROR (app bug): local reference table overflow (max=8388608)
        local reference table dump:
          Last 10 entries (of 8388608):
            8388607: 0x1b4007c0 byte[] (1 elements)
            8388606: 0x1b4007b0 byte[] (1 elements)
            8388605: 0x1b4007a0 byte[] (1 elements)
            8388604: 0x1b400790 byte[] (1 elements)
            8388603: 0x1b400780 byte[] (1 elements)
            8388602: 0x1b400770 byte[] (1 elements)
            8388601: 0x1b400760 byte[] (1 elements)
            8388600: 0x1b400750 byte[] (1 elements)
            8388599: 0x1b400740 byte[] (1 elements)
            8388598: 0x1b400730 byte[] (1 elements)
          Summary:
            8388607 of byte[] (1 elements) (8388607 unique instances)
                1 of java.lang.Thread
         Resizing failed: Requested size exceeds maximum: 16777216'
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     x0  0000000000000000  x1  0000000000003780  x2  0000000000000006  x3  0000007417e7c9f0
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     x4  fefeff740442df97  x5  fefeff740442df97  x6  fefeff740442df97  x7  7f7f7f7f7f7fffff
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     x8  00000000000000f0  x9  d0dac69971875631  x10 0000000000000001  x11 0000000000000000
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     x12 fffffff0fffffbdf  x13 ffffffffffffffff  x14 0000000000000004  x15 ffffffffffffffff
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     x16 0000007505532738  x17 0000007505510be0  x18 0000007416f64000  x19 000000000000374d
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     x20 0000000000003780  x21 00000000ffffffff  x22 00000074768c5e00  x23 0000007481cff1c3
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     x24 0000007481cdf247  x25 000000748221b000  x26 00000074822fd258  x27 000000748221b000
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     x28 0000000000000043  x29 0000007417e7ca90
    2019-12-27 21:20:38.543 14219-14219/? A/DEBUG:     sp  0000007417e7c9d0  lr  00000075054c2404  pc  00000075054c2430
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG: backtrace:
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #00 pc 0000000000073430  /apex/com.android.runtime/lib64/bionic/libc.so (abort+160) (BuildId: a2584ee8458a61d422edf24b4cd23b78)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #01 pc 00000000004b8650  /apex/com.android.runtime/lib64/libart.so (art::Runtime::Abort(char const*)+2280) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #02 pc 000000000000b458  /system/lib64/libbase.so (android::base::LogMessage::~LogMessage()+580) (BuildId: 1efae6b19d52bd307d764e26e1d0e2c9)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #03 pc 00000000003c67c0  /apex/com.android.runtime/lib64/libart.so (art::JNI::NewByteArray(_JNIEnv*, int)+1428) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #04 pc 0000000000371dfc  /apex/com.android.runtime/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewPrimitiveArray(char const*, _JNIEnv*, int, art::Primitive::Type)+932) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #05 pc 0000000000000f68  /data/app/com.wsy.jnidemo-EKfsEZ5DLE64_7yrLJJNVA==/lib/arm64/libreftest.so (_JNIEnv::NewByteArray(int)+36) (BuildId: f8de44f2f2443e0b50ce67aad7bbdfa9ffdecf7a)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #06 pc 0000000000000fec  /data/app/com.wsy.jnidemo-EKfsEZ5DLE64_7yrLJJNVA==/lib/arm64/libreftest.so (Java_com_wsy_jnidemo_test_ReferenceTest_createLocalRef+52) (BuildId: f8de44f2f2443e0b50ce67aad7bbdfa9ffdecf7a)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #07 pc 000000000013f350  /apex/com.android.runtime/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #08 pc 00000000001365b8  /apex/com.android.runtime/lib64/libart.so (art_quick_invoke_static_stub+568) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #09 pc 000000000014500c  /apex/com.android.runtime/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+276) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #10 pc 00000000002e281c  /apex/com.android.runtime/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+384) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #11 pc 00000000002dda7c  /apex/com.android.runtime/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+892) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #12 pc 00000000005a2adc  /apex/com.android.runtime/lib64/libart.so (MterpInvokeStatic+372) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #13 pc 0000000000130994  /apex/com.android.runtime/lib64/libart.so (mterp_op_invoke_static+20) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #14 pc 0000000000012df4  [anon:dalvik-classes2.dex extracted in memory from /data/app/com.wsy.jnidemo-EKfsEZ5DLE64_7yrLJJNVA==/base.apk!classes2.dex] (com.wsy.jnidemo.MainActivity.doReferenceTest+12)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #15 pc 00000000005a25d4  /apex/com.android.runtime/lib64/libart.so (MterpInvokeDirect+1100) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #16 pc 0000000000130914  /apex/com.android.runtime/lib64/libart.so (mterp_op_invoke_direct+20) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #17 pc 0000000000012d24  [anon:dalvik-classes2.dex extracted in memory from /data/app/com.wsy.jnidemo-EKfsEZ5DLE64_7yrLJJNVA==/base.apk!classes2.dex] (com.wsy.jnidemo.MainActivity.access$000)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #18 pc 00000000005a2d78  /apex/com.android.runtime/lib64/libart.so (MterpInvokeStatic+1040) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #19 pc 0000000000130994  /apex/com.android.runtime/lib64/libart.so (mterp_op_invoke_static+20) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #20 pc 0000000000012cc8  [anon:dalvik-classes2.dex extracted in memory from /data/app/com.wsy.jnidemo-EKfsEZ5DLE64_7yrLJJNVA==/base.apk!classes2.dex] (com.wsy.jnidemo.MainActivity$1.run+4)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #21 pc 00000000005a1ae8  /apex/com.android.runtime/lib64/libart.so (MterpInvokeInterface+1788) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #22 pc 0000000000130a14  /apex/com.android.runtime/lib64/libart.so (mterp_op_invoke_interface+20) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #23 pc 00000000000ec0f0  /apex/com.android.runtime/javalib/core-oj.jar (java.lang.Thread.run+8)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #24 pc 00000000002b3b30  /apex/com.android.runtime/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEbb.llvm.3929369822492601747+240) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #25 pc 0000000000591570  /apex/com.android.runtime/lib64/libart.so (artQuickToInterpreterBridge+1032) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #26 pc 000000000013f468  /apex/com.android.runtime/lib64/libart.so (art_quick_to_interpreter_bridge+88) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #27 pc 0000000000136334  /apex/com.android.runtime/lib64/libart.so (art_quick_invoke_stub+548) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #28 pc 0000000000144fec  /apex/com.android.runtime/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+244) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #29 pc 00000000004aff10  /apex/com.android.runtime/lib64/libart.so (art::(anonymous namespace)::InvokeWithArgArray(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::(anonymous namespace)::ArgArray*, art::JValue*, char const*)+104) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #30 pc 00000000004b1024  /apex/com.android.runtime/lib64/libart.so (art::InvokeVirtualOrInterfaceWithJValues(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, jvalue const*)+416) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #31 pc 00000000004f19ec  /apex/com.android.runtime/lib64/libart.so (art::Thread::CreateCallback(void*)+1176) (BuildId: 615724283373fd93e5fb77101fe57dab)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #32 pc 00000000000d6b70  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36) (BuildId: a2584ee8458a61d422edf24b4cd23b78)
    2019-12-27 21:20:38.606 14219-14219/? A/DEBUG:       #33 pc 0000000000074eac  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: a2584ee8458a61d422edf24b4cd23b78)
    
  • 源码阅读

    局部变量表对象定义在jni_env_ext.h

      // JNI local references.
      IndirectReferenceTable locals_ GUARDED_BY(Locks::mutator_lock_);
    

    当我们在进行一些JNI操作时,会进行添加,例如以下函数

    jni_internal.cc中的JNI函数的实现

     static jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID mid, ...) {
        va_list ap;
        va_start(ap, mid);
        ScopedVAArgs free_args_later(&ap);
        CHECK_NON_NULL_ARGUMENT(obj);
        CHECK_NON_NULL_ARGUMENT(mid);
        ScopedObjectAccess soa(env);
        JValue result(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap));
        return soa.AddLocalReference<jobject>(result.GetL());
      }
    
     static jstring NewStringUTF(JNIEnv* env, const char* utf) {
        if (utf == nullptr) {
          return nullptr;
        }
        ScopedObjectAccess soa(env);
        ObjPtr<mirror::String> result = mirror::String::AllocFromModifiedUtf8(soa.Self(), utf);
        return soa.AddLocalReference<jstring>(result);
      }
    

    jni_env_ext-inl.h中,AddLocalReference的实现如下,调用了locals_Add

    template<typename T>
    inline T JNIEnvExt::AddLocalReference(ObjPtr<mirror::Object> obj) {
      std::string error_msg;
      IndirectRef ref = locals_.Add(local_ref_cookie_, obj, &error_msg);
      if (UNLIKELY(ref == nullptr)) {
        // This is really unexpected if we allow resizing local IRTs...
        LOG(FATAL) << error_msg;
        UNREACHABLE();
      }
        
      // TODO: fix this to understand PushLocalFrame, so we can turn it on.
      if (false) {
        if (check_jni_) {
          size_t entry_count = locals_.Capacity();
          if (entry_count > 16) {
             locals_.Dump(LOG_STREAM(WARNING) << "Warning: more than 16 JNI local references: "
                                            << entry_count << " (most recent was a "
                                            << mirror::Object::PrettyTypeOf(obj) << ")\n");
          // TODO: LOG(FATAL) in a later release?
          }
        }
      }
        
      return reinterpret_cast<T>(ref);
    }
    

    若运行过程中Add过多就会溢出,为了防止溢出,我们需要调用DeleteLocalRef方法,内部调用了locals_Remove

      static void DeleteLocalRef(JNIEnv* env, jobject obj) {
        if (obj == nullptr) {
          return;
        }
        // SOA is only necessary to have exclusion between GC root marking and removing.
        // We don't want to have the GC attempt to mark a null root if we just removed
        // it. b/22119403
        ScopedObjectAccess soa(env);
        auto* ext_env = down_cast<JNIEnvExt*>(env);
        if (!ext_env->locals_.Remove(ext_env->local_ref_cookie_, obj)) {
          // Attempting to delete a local reference that is not in the
          // topmost local reference frame is a no-op.  DeleteLocalRef returns
          // void and doesn't throw any exceptions, but we should probably
          // complain about it so the user will notice that things aren't
          // going quite the way they expect.
          LOG(WARNING) << "JNI WARNING: DeleteLocalRef(" << obj << ") "
                       << "failed to find entry";
        }
      }
    

    在函数运行结束,调用栈被Pop时,会执行以下内容:

    jni_env_ext.cc

    void JNIEnvExt::PopFrame() {
      locals_.SetSegmentState(local_ref_cookie_);
      local_ref_cookie_ = stacked_local_ref_cookies_.back();
      stacked_local_ref_cookies_.pop_back();
    }
    

    因此我们循环调用时,不会溢出。

3. GlobalReference

全局引用,如果我们希望在native层保存一个Java对象使用,那我们可以选择全局引用,全局引用的使用方式是env->NewGlobalRef(obj),返回的虽然也是jobject,但它是一个全局引用,是可以保存下来后续使用的。

对于全局引用,我们需要妥善管理,每一个主动创建的全局引用在最后不使用时都要调用env->DeleteGlobalRef(obj)释放。否则也会导致溢出或者内存溢出。

  • 示例操作

类似于上述的LocalRefrence操作,我们循环创建全局引用进行测试,但是和上述的LocalRefrence操作不同的是,这里我们将创建GlobalReference的循环放在Java层进行验证

正确示例

extern "C" JNIEXPORT void
JNICALL
Java_com_wsy_jnidemo_test_ReferenceTest_createGlobalRef(
        JNIEnv *env,
        jclass) {
    jobject globalRef = env->NewGlobalRef(env->NewByteArray(1));
    env->DeleteGlobalRef(globalRef);
}

错误示例

extern "C" JNIEXPORT void
JNICALL
Java_com_wsy_jnidemo_test_ReferenceTest_createGlobalRef(
        JNIEnv *env,
        jclass) {
    jobject globalRef = env->NewGlobalRef(env->NewByteArray(1));
}

Java代码

    for (int i = 0; i < 50000000; i++) {
        ReferenceTest.createGlobalRef();
    }

以如上的Java代码进行调用,错误示例的运行效果如下,引用表溢出

2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666] JNI ERROR (app bug): global reference table overflow (max=51200)global reference table dump:
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]   Last 10 entries (of 51200):
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51199: 0x13ac4c70 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51198: 0x13ac4c60 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51197: 0x13ac4c50 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51196: 0x13ac4c40 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51195: 0x13ac4c30 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51194: 0x13ac4c20 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51193: 0x13ac4c10 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51192: 0x13ac4c00 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51191: 0x13ac4bf0 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     51190: 0x13ac4be0 byte[] (1 elements)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]   Summary:
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]     50250 of byte[] (1 elements) (50250 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]       604 of java.nio.DirectByteBuffer (604 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]       317 of java.lang.Class (244 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         3 of android.opengl.EGLDisplay (2 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         3 of android.opengl.EGLSurface (2 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         3 of android.opengl.EGLContext (2 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         2 of dalvik.system.PathClassLoader (1 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         2 of java.lang.String (2 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         2 of java.lang.ThreadGroup (2 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         2 of java.lang.ref.WeakReference (2 unique instances)
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of com.qualcomm.qti.Performance$PerfServiceDeathRecipient
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of dalvik.system.VMRuntime
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.app.ActivityThread$ApplicationThread
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.os.Binder
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.graphics.HardwareRenderer$ProcessInitializer$1
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.view.WindowManagerGlobal$1
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.view.inputmethod.InputMethodManager$1
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.hardware.display.DisplayManagerGlobal$DisplayManagerCallback
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.view.accessibility.AccessibilityManager$1
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.os.PersistableBundle$1
2019-12-28 10:58:26.301 2354-2415/com.wsy.jnidemo A/com.wsy.jnidem: java_vm_ext.cc:666]         1 of android.view.ViewRootImpl$W
  • 源码解析

    和局部变量表不同,全局变量表对象定义在java_vm_ext.h

      // Not guarded by globals_lock since we sometimes use SynchronizedGet in Thread::DecodeJObject.
      IndirectReferenceTable globals_;
    

    jni_internal.cc中,NewGlobalRef的实现如下,会调用JavaVM的AddGlobalRef

      static jobject NewGlobalRef(JNIEnv* env, jobject obj) {
        ScopedObjectAccess soa(env);
        ObjPtr<mirror::Object> decoded_obj = soa.Decode<mirror::Object>(obj);
        return soa.Vm()->AddGlobalRef(soa.Self(), decoded_obj);
      }
    

    java_vm_ext.cc中,AddGlobalRef的实现如下,会添加全局引用到全局引用表中

    jobject JavaVMExt::AddGlobalRef(Thread *self, ObjPtr <mirror::Object> obj) {
        // Check for null after decoding the object to handle cleared weak globals.
        if (obj == nullptr) {
            return nullptr;
        }
        IndirectRef ref;
        std::string error_msg;
        {
            WriterMutexLock mu(self, *Locks::jni_globals_lock_);
            ref = globals_.Add(kIRTFirstSegment, obj, &error_msg);
        }
        if (UNLIKELY(ref == nullptr)) {
            LOG(FATAL) << error_msg;
            UNREACHABLE();
        }
        CheckGlobalRefAllocationTracking();
        return reinterpret_cast<jobject>(ref);
    }
    

    Add的实现详见上述引用表的实现介绍,和局部引用表一样,若运行过程中添加的引用过多,globals_就会溢出,而且和局部引用表不同,对于全局引用表,在函数执行完时,其数据不会被移除,因此我们需要主动调用DeleteGlobalRef进行释放。为了防止溢出,我们需要调用DeleteGlobalRef方法,内部调用了globals_Remove函数。

    jni_internal.cc中,DeleteGlobalRef的实现如下

      static void DeleteGlobalRef(JNIEnv* env, jobject obj) {
        JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->GetVm();
        Thread* self = down_cast<JNIEnvExt*>(env)->self_;
        vm->DeleteGlobalRef(self, obj);
      }
    

    java_vm_ext.cc中,DeleteGlobalRef的实现如下,会在globals_表中移除全局引用对象

    void JavaVMExt::DeleteGlobalRef(Thread *self, jobject obj) {
        if (obj == nullptr) {
            return;
        }
        {
            WriterMutexLock mu(self, *Locks::jni_globals_lock_);
            if (!globals_.Remove(kIRTFirstSegment, obj)) {
                LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") "
                             << "failed to find entry";
            }
        }
        CheckGlobalRefAllocationTracking();
    }
    

4. WeakGlobalReference

全局弱引用,如果我们希望在native层保存一个Java对象使用,那我们也可以选择全局弱引用,全局弱引用的使用方式是env->NewWeakGlobalRef(obj),返回的虽然也是jobject,但它是一个全局弱引用,是可以保存下来后续使用的。

对于全局弱引用,我们需要妥善管理,每一个主动创建的全局引用在最后不使用时都要调用env->DeleteWeakGlobalRef(obj)释放。否则也会导致溢出或者内存溢出。

  • 示例操作
  1. 在内部进行循环,使内存溢出

    Java

    ReferenceTest.createWeakGlobalRef();
    

    C++

    extern "C" JNIEXPORT void
    JNICALL
    Java_com_wsy_jnidemo_test_ReferenceTest_createWeakGlobalRef(
            JNIEnv *env,
            jclass) {
        for (int i = 0; i < 50000; i++) {
            jobject weakGlobalRef = env->NewWeakGlobalRef(env->NewByteArray(50000));
        }
    }
    

    日志如下

    2019-12-28 21:40:50.142 6391-6391/? A/DEBUG: Abort message: 'JNI DETECTED ERROR IN APPLICATION: JNI NewWeakGlobalRef called with pending exception java.lang.OutOfMemoryError: Failed to allocate a 50016 byte allocation with 880 free bytes and 880B until OOM, target footprint 536870912, growth limit 536870912
            at void com.wsy.jnidemo.test.ReferenceTest.createWeakGlobalRef() (ReferenceTest.java:-2)
            at void com.wsy.jnidemo.MainActivity.doReferenceTest() (MainActivity.java:66)
            at void com.wsy.jnidemo.MainActivity.access$000(com.wsy.jnidemo.MainActivity) (MainActivity.java:15)
            at void com.wsy.jnidemo.MainActivity$1.run() (MainActivity.java:45)
            at void java.lang.Thread.run() (Thread.java:919)
        
            in call to NewWeakGlobalRef
            from void com.wsy.jnidemo.test.ReferenceTest.createWeakGlobalRef()'
    
  2. 在外部进行循环,并不会造成内存溢出

    Java

        for (int i = 0; i < 50000; i++) {
            ReferenceTest.createWeakGlobalRef();
        }
    

    C++

        extern "C" JNIEXPORT void
        JNICALL
        Java_com_wsy_jnidemo_test_ReferenceTest_createWeakGlobalRef(
                JNIEnv *env,
                jclass) {
                jobject weakGlobalRef = env->NewWeakGlobalRef(env->NewByteArray(50000));
        }
    

    这样不会导致crash,因为局部引用表的的对象对应的Java对象会被gc,但是会造成引用表中会多出50000个对象,久而久之会造成引用表溢出。

  3. 在外部进行循环,使引用表溢出

    Java

        for (long i = 0; i < 2500000000L; i++) {
            ReferenceTest.createWeakGlobalRef();
        }
    

    C++

        extern "C" JNIEXPORT void
        JNICALL
        Java_com_wsy_jnidemo_test_ReferenceTest_createWeakGlobalRef(
                JNIEnv *env,
                jclass) {
                jobject weakGlobalRef = env->NewWeakGlobalRef(env->NewByteArray(1));
        }
    

    这样会造成引用表溢出,错误信息如下

    2019-12-28 21:56:20.866 7937-7937/? A/DEBUG: Abort message: 'JNI ERROR (app bug): weak global reference table overflow (max=51200)weak global reference table dump:
          Last 10 entries (of 51200):
            51199: 0x133c8580 byte[] (1 elements)
            51198: 0x133c8570 byte[] (1 elements)
            51197: 0x133c8560 byte[] (1 elements)
            51196: 0x133c8550 byte[] (1 elements)
            51195: 0x133c8540 byte[] (1 elements)
            51194: 0x133c8530 byte[] (1 elements)
            51193: 0x133c8520 byte[] (1 elements)
            51192: 0x133c8510 byte[] (1 elements)
            51191: 0x133c8500 byte[] (1 elements)
            51190: 0x133c84f0 byte[] (1 elements)
          Summary:
            51163 of byte[] (1 elements) (51163 unique instances)
               27 of java.lang.DexCache (27 unique instances)
                9 of dalvik.system.PathClassLoader (6 unique instances)
                1 of java.lang.BootClassLoader
        '
    2019-12-28 21:56:20.866 7937-7937/? A/DEBUG:     x0  0000000000000000  x1  0000000000001ef7  x2  0000000000000006  x3  0000007417e93890
    2019-12-28 21:56:20.866 7937-7937/? A/DEBUG:     x4  fefeff740442df97  x5  fefeff740442df97  x6  fefeff740442df97  x7  7f7f7f7f7f7fffff
    2019-12-28 21:56:20.866 7937-7937/? A/DEBUG:     x8  00000000000000f0  x9  d0dac69971875631  x10 0000000000000001  x11 0000000000000000
    2019-12-28 21:56:20.866 7937-7937/? A/DEBUG:     x12 fffffff0fffffbdf  x13 ffffffffffffffff  x14 0000000000000004  x15 ffffffffffffffff
    2019-12-28 21:56:20.866 7937-7937/? A/DEBUG:     x16 0000007505532738  x17 0000007505510be0  x18 000000741781c000  x19 0000000000001ec8
    2019-12-28 21:56:20.866 7937-7937/? A/DEBUG:     x20 0000000000001ef7  x21 00000000ffffffff  x22 000000747685e280  x23 0000007481cff1c3
    2019-12-28 21:56:20.867 7937-7937/? A/DEBUG:     x24 0000007481cdf247  x25 000000748221b000  x26 00000074822fd258  x27 000000748221b000
    2019-12-28 21:56:20.867 7937-7937/? A/DEBUG:     x28 0000000000000043  x29 0000007417e93930
    2019-12-28 21:56:20.867 7937-7937/? A/DEBUG:     sp  0000007417e93870  lr  00000075054c2404  pc  00000075054c2430
    
  • 源码阅读

    GlobalReference不同,WeakGlobalReference是可以被gc回收的,以下函数会在标记清除时被调用,

    void JavaVMExt::SweepJniWeakGlobals(IsMarkedVisitor *visitor) {
        MutexLock mu(Thread::Current(), *Locks::jni_weak_globals_lock_);
        Runtime *const runtime = Runtime::Current();
        for (auto *entry : weak_globals_) {
            // Need to skip null here to distinguish between null entries and cleared weak ref entries.
            if (!entry->IsNull()) {
                // Since this is called by the GC, we don't need a read barrier.
                mirror::Object *obj = entry->Read<kWithoutReadBarrier>();
                mirror::Object *new_obj = visitor->IsMarked(obj);
                if (new_obj == nullptr) {
                    new_obj = runtime->GetClearedJniWeakGlobal();
                }
                *entry = GcRoot<mirror::Object>(new_obj);
            }
        }
    }