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

在前面的文章中,咱們已經瞭解了JNI的工程結構、調用流程、異常處理等知識,本文將介紹JNI中的引用管理。java

1、引用類型

JNI中封裝瞭如下三種引用類型:android

  • LocalReference數組

    局部引用,引用表的持有者是JNIEnv,在函數執行完時會自動釋放,可是在函數執行過程當中建立過多就會致使內存溢出或引用表溢出。bash

  • GlobalReferencecookie

    全局強引用,引用表的持有者是JVM,引用對象不會被gc,手動建立,手動釋放,若是建立過多而且不釋放會致使內存溢出或引用表溢出。app

  • WeakGlobalReferenceionic

    全局弱引用,引用表的持有者是JVM,引用對象可能會被gc,手動建立,手動釋放,若是建立過多而且不釋放會致使內存溢出或引用表溢出。ide

2、各個引用的詳細介紹

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);
            }
        }
    }
    複製代碼
相關文章
相關標籤/搜索