在前面的文章中,我们已经了解了JNI的工程结构、调用流程、异常处理等知识,本文将介绍JNI中的引用管理。
JNI中封装了以下三种引用类型:
局部引用,引用表的持有者是 JNIEnv
,在函数执行完时会自动释放,但是在函数执行过程中创建过多就会导致内存溢出或引用表溢出。
全局强引用,引用表的持有者是 JVM
,引用对象不会被gc,手动创建,手动释放,如果创建过多并且不释放会导致内存溢出或引用表溢出。
全局弱引用,引用表的持有者是 JVM
,引用对象可能会被gc,手动创建,手动释放,如果创建过多并且不释放会导致内存溢出或引用表溢出。
引用表的数据类型为 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; } 复制代码
局部引用,我们调用的 FindClass
、 GetObjectClass
、 NewStringUTF
、 NewObjectArray
、 NewByteArray
、 CallObjectMethod
等函数回传的都是局部引用。在没有循环处理的情况下,我们可以让其自动释放,但是在循环次数较多的循环中,需要手动释放。如果局部引用表溢出,就会出现异常。需要注意的是,局部引用不能保存在全局变量中使用,否则会报 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(); } 复制代码
因此我们循环调用时,不会溢出。
全局引用,如果我们希望在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(); } 复制代码
全局弱引用,如果我们希望在native层保存一个Java对象使用,那我们也可以选择全局弱引用,全局弱引用的使用方式是 env->NewWeakGlobalRef(obj)
,返回的虽然也是 jobject
,但它是一个全局弱引用,是可以保存下来后续使用的。
对于全局弱引用,我们需要妥善管理,每一个主动创建的全局引用在最后不使用时都要调用 env->DeleteWeakGlobalRef(obj)
释放。否则也会导致溢出或者内存溢出。
在内部进行循环,使内存溢出
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()' 复制代码
在外部进行循环,并不会造成内存溢出
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个对象,久而久之会造成引用表溢出。
在外部进行循环,使引用表溢出
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); } } } 复制代码