在前面的文章中,咱們已經瞭解了JNI的工程結構、調用流程、異常處理等知識,本文將介紹JNI中的引用管理。java
JNI中封裝瞭如下三種引用類型:android
LocalReference數組
局部引用,引用表的持有者是JNIEnv
,在函數執行完時會自動釋放,可是在函數執行過程當中建立過多就會致使內存溢出或引用表溢出。bash
GlobalReferencecookie
全局強引用,引用表的持有者是JVM
,引用對象不會被gc,手動建立,手動釋放,若是建立過多而且不釋放會致使內存溢出或引用表溢出。app
WeakGlobalReferenceionic
全局弱引用,引用表的持有者是JVM
,引用對象可能會被gc,手動建立,手動釋放,若是建立過多而且不釋放會致使內存溢出或引用表溢出。ide
引用表的數據類型爲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);
}
}
}
複製代碼