日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JNI实现源码分析【三 间接引用表】

發布時間:2025/3/15 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JNI实现源码分析【三 间接引用表】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在JNI實現源碼分析【二 數據結構】的參數傳遞一節中,我們提到,JNI為了安全性的考慮使用了形如jobject的結構來傳遞參數。而jobject被表述為指針,但又不是直接指向Object的指針那么jobject是如何和真正的Object建立聯系呢?
在JNI的API中,有一組API Global and Local References,這里的References又是什么?為啥會有這一組API?
答案都和間接引用表(IndirectRefTable)有關

0x01: IndirectRefTable

源碼見IndirectRefTable.h
代碼很復雜,等效理解就可以了,其作用就是一張保存了間接引用的表。讓jobject和Object建立起聯系。

0x02: 作用域

在JNI中,有兩個不同的作用域:全局作用域(進程級別)和線程作用域(線程級別)。這兩個作用域分別有自己的間接引用表。
全局作用域的間接引用表保存在gDvm.jniGlobalRefTable中。gDvm是一個全局變量,在虛擬機啟動的時候就創建。
線程作用域的間接引用表保存在thread.jniLocalRefTable中。和線程綁定,線程創建時創建,線程銷毀時銷毀。

JNI API中的全局引用和局部引用,指的就是全局作用域的間接引用表和線程作用域的間接引用表。

于是:

jobject NewGlobalRef(JNIEnv *env, jobject obj); void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

我們就是操作了全局引用表

而:

jobject NewLocalRef(JNIEnv *env, jobject ref); void DeleteLocalRef(JNIEnv *env, jobject localRef);

我們操作了線程引用表

讓我們再來看看兩個表的大小,在創建的時候,就指定了其大小:

#define kGlobalRefsTableInitialSize 512 #define kGlobalRefsTableMaxSize 51200 if (!gDvm.jniGlobalRefTable.init(kGlobalRefsTableInitialSize,kGlobalRefsTableMaxSize,kIndirectKindGlobal)) {return false;}

可以看到,全局引用表的初始大小為512,最大為51200。

#define kJniLocalRefMin 64 #define kJniLocalRefMax 512 if (!thread->jniLocalRefTable.init(kJniLocalRefMin,kJniLocalRefMax, kIndirectKindLocal)) {return false;}

而局部引用表的初始大小為64,最大為512。 這里順便提一下,當超過這個最大時,就會報local reference table overflow (max=512)的錯誤。

0x03: jobject到Object的映射

到現在,我們應該可以順理成章的理解到,jobject到Object的映射借用了間接引用表,沒錯!
我們來分析局部引用,全局引用是類似的。

static inline jobject addLocalReference(Thread* self, Object* obj) {if (obj == NULL) {return NULL;}IndirectRefTable* pRefTable = &self->jniLocalRefTable;void* curFrame = self->interpSave.curFrame;u4 cookie = SAVEAREA_FROM_FP(curFrame)->xtra.localRefCookie;jobject jobj = (jobject) pRefTable->add(cookie, obj);if (UNLIKELY(jobj == NULL)) {AddLocalReferenceFailure(pRefTable);}if (UNLIKELY(gDvmJni.workAroundAppJniBugs)) {// Hand out direct pointers to support broken old apps.return reinterpret_cast<jobject>(obj);}return jobj; }

非常明了的代碼,我們使用了pRefTable->add將實際對象添加到了間接引用表,從而獲取了jobject的間接引用。

我們看一下add做了啥:

IndirectRef IndirectRefTable::add(u4 cookie, Object* obj) {IRTSegmentState prevState;prevState.all = cookie;size_t topIndex = segmentState.parts.topIndex;assert(obj != NULL);assert(dvmIsHeapAddress(obj));assert(table_ != NULL);assert(alloc_entries_ <= max_entries_);assert(segmentState.parts.numHoles >= prevState.parts.numHoles);/** 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;IndirectRefSlot* slot;int numHoles = segmentState.parts.numHoles - prevState.parts.numHoles;if (numHoles > 0) {assert(topIndex > 1);/* find the first hole; likely to be near the end of the list,* we know the item at the topIndex is not a hole */slot = &table_[topIndex - 1];assert(slot->obj != NULL);while ((--slot)->obj != NULL) {assert(slot >= table_ + prevState.parts.topIndex);}segmentState.parts.numHoles--;} else {/* add to the end, grow if needed */if (topIndex == alloc_entries_) {/* reached end of allocated space; did we hit buffer max? */if (topIndex == max_entries_) {ALOGE("JNI ERROR (app bug): %s reference table overflow (max=%d)",indirectRefKindToString(kind_), max_entries_);return NULL;}size_t newSize = alloc_entries_ * 2;if (newSize > max_entries_) {newSize = max_entries_;}assert(newSize > alloc_entries_);IndirectRefSlot* newTable =(IndirectRefSlot*) realloc(table_, newSize * sizeof(IndirectRefSlot));if (table_ == NULL) {ALOGE("JNI ERROR (app bug): unable to expand %s reference table ""(from %d to %d, max=%d)",indirectRefKindToString(kind_),alloc_entries_, newSize, max_entries_);return NULL;}memset(newTable + alloc_entries_, 0xd1,(newSize - alloc_entries_) * sizeof(IndirectRefSlot));alloc_entries_ = newSize;table_ = newTable;}slot = &table_[topIndex++];segmentState.parts.topIndex = topIndex;}slot->obj = obj;slot->serial = nextSerial(slot->serial);result = toIndirectRef(slot - table_, slot->serial, kind_);assert(result != NULL);return result; }

我擦,真的是太復雜了,里面肯定包含了某個算法,反正就是通過參數cookie,通過slot等,在表的合適位置引用了真正的Object,然后返回了一個值(間接引用),后續通過這個值,能夠去表里面的這個位置找到Object。
所以之前說過,jobject并不是直接指向Object的指針。甚至它并不是真正的地址,它僅僅是表的間接引用。

讓我們繼續看看,如何通過這個間接引用找到真實的Object吧:

Object* dvmDecodeIndirectRef(Thread* self, jobject jobj) {if (jobj == NULL) {return NULL;}switch (indirectRefKind(jobj)) {case kIndirectKindLocal:{Object* result = self->jniLocalRefTable.get(jobj);if (UNLIKELY(result == NULL)) {ALOGE("JNI ERROR (app bug): use of deleted local reference (%p)", jobj);ReportJniError();}return result;}case kIndirectKindGlobal:{// TODO: find a way to avoid the mutex activity hereIndirectRefTable* pRefTable = &gDvm.jniGlobalRefTable;ScopedPthreadMutexLock lock(&gDvm.jniGlobalRefLock);Object* result = pRefTable->get(jobj);if (UNLIKELY(result == NULL)) {ALOGE("JNI ERROR (app bug): use of deleted global reference (%p)", jobj);ReportJniError();}return result;}case kIndirectKindWeakGlobal:{// TODO: find a way to avoid the mutex activity hereIndirectRefTable* pRefTable = &gDvm.jniWeakGlobalRefTable;ScopedPthreadMutexLock lock(&gDvm.jniWeakGlobalRefLock);Object* result = pRefTable->get(jobj);if (result == kClearedJniWeakGlobal) {result = NULL;} else if (UNLIKELY(result == NULL)) {ALOGE("JNI ERROR (app bug): use of deleted weak global reference (%p)", jobj);ReportJniError();}return result;}case kIndirectKindInvalid:default:if (UNLIKELY(gDvmJni.workAroundAppJniBugs)) {// Assume an invalid local reference is actually a direct pointer.return reinterpret_cast<Object*>(jobj);}ALOGW("Invalid indirect reference %p in decodeIndirectRef", jobj);ReportJniError();return kInvalidIndirectRefObject;} }

可以看到,通過jobject可以計算出RefKind,即jobject還包含了類型信息。
真正找Object是在pRefTable->get:

Object* IndirectRefTable::get(IndirectRef iref) const {IndirectRefKind kind = indirectRefKind(iref);if (kind != kind_) {if (iref == NULL) {ALOGW("Attempt to look up NULL %s reference", indirectRefKindToString(kind_));return kInvalidIndirectRefObject;}if (kind == kIndirectKindInvalid) {ALOGE("JNI ERROR (app bug): invalid %s reference %p",indirectRefKindToString(kind_), iref);abortMaybe();return kInvalidIndirectRefObject;}// References of the requested kind cannot appear within this table.return kInvalidIndirectRefObject;}u4 topIndex = segmentState.parts.topIndex;u4 index = extractIndex(iref);if (index >= topIndex) {/* bad -- stale reference? */ALOGE("JNI ERROR (app bug): accessed stale %s reference %p (index %d in a table of size %d)",indirectRefKindToString(kind_), iref, index, topIndex);abortMaybe();return kInvalidIndirectRefObject;}Object* obj = table_[index].obj;if (obj == NULL) {ALOGI("JNI ERROR (app bug): accessed deleted %s reference %p",indirectRefKindToString(kind_), iref);abortMaybe();return kInvalidIndirectRefObject;}u4 serial = extractSerial(iref);if (serial != table_[index].serial) {ALOGE("JNI ERROR (app bug): attempt to use stale %s reference %p",indirectRefKindToString(kind_), iref);abortMaybe();return kInvalidIndirectRefObject;}return obj; }

和我們前面想的一樣,這里通過一堆的計算后,在Object* obj = table_[index].obj;處找到了真實的Object。

0x04: JNI在背后默默做的事

在JNI環境中,我們永遠接觸不了真實的Object對象,上面映射方法是虛擬機內部的,我們在JNI環境也是沒法調用的。所以,我們在JNI環境中,使用的都是間接引用,比如jobject,jmethodID等。確實,JNI的所有API都在使用這些間接引用。
那么,這里就有一個問題了,既然間接引用和間接引用表有關,那在使用JNI的API時,獲取到這些間接引用時,JNI將真實的對象保存在哪個表里面?
答案是線程引用表,幾乎每一個API都有JNIEnv,JNIEnv和線程綁定,可以很容易定位到線程引用表。放到線程應用表,隨著線程的銷毀,引用表也不會被銷毀,不會一直占用空間。

我當初在JNI中想要獲取Throwable.printStackTrace時,就因為調用相關的API,然后產生了很多的間接引用,將間接引用表撐爆,報了:local reference table overflow (max=512)。

除了JNI的默認行為,假如我們想要自己控制引用的生命周期,比如提前刪除,將引用放置到全局引用表等,我們可以使用Ref相關的API即可,記住,不用了一定要刪除,不要存在引用泄漏。



作者:difcareer
鏈接:http://www.jianshu.com/p/127adc130508
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。 與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的JNI实现源码分析【三 间接引用表】的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。