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
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
JNI实现源码分析【三 间接引用表】相关推荐
- Nouveau源码分析(三):NVIDIA设备初始化之nouveau_drm_probe
Nouveau源码分析(三) 向DRM注册了Nouveau驱动之后,内核中的PCI模块就会扫描所有没有对应驱动的设备,然后和nouveau_drm_pci_table对照. 对于匹配的设备,PCI模块 ...
- 【投屏】Scrcpy源码分析三(Client篇-投屏阶段)
Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...
- Spring源码分析(三)
Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...
- 【转】ABP源码分析三十五:ABP中动态WebAPI原理解析
动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...
- 【转】ABP源码分析三十三:ABP.Web
ABP.Web模块并不复杂,主要完成ABP系统的初始化和一些基础功能的实现. AbpWebApplication : 继承自ASP.Net的HttpApplication类,主要完成下面三件事 一,在 ...
- 【转】ABP源码分析三:ABP Module
Abp是基于模块化设计思想进行构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modul ...
- ABP源码分析三十四:ABP.Web.Mvc
ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...
- 【转】ABP源码分析三十四:ABP.Web.Mvc
ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...
- 【转】ABP源码分析三十一:ABP.AutoMapper
这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...
最新文章
- 10 款 VS Code 插件神器,第 7 款超级实用!
- 如何在算法交易中使用AI?摩根大通发布新版指南
- 关于深度学习的小知识点
- pymysql.err.InterfaceError: (0, '')
- C# linq创建嵌套组
- hashMap与hashTable区别
- 【机器学习基础】如何在Python中处理不平衡数据
- springquartz的LocalDataSourceJobStore
- 6个很棒的PostCSS插件,让您成为一个CSS向导
- linux arm mmu基础【转】
- 嵌入式操作系统内核原理和开发(地址空间)
- Leetcode 5182.删除一次得到子数组最大和
- Ant之build.xml详解
- Kubernetes架构图 Architecture Workload Networking Storage RBAC
- linux rpm解压命令,LINUX下各种解压命令收藏
- 3dsMax批量设置可编辑样条线的视口渲染
- 根据ip地址定位城市信息
- 学习verilog的经典好教材与资料
- 华为服务器linux光驱名称,华为服务器通过mgmt口挂载光盘装系统及Linux系统rescue模式下修复内核...
- 仿原生安卓文件管理器
热门文章
- PROTEUS串口仿真遇到的BUG(转载)
- MOSS点滴(1):如何开发和部署feature
- 高性能计算的线程模型:Pthreads 还是 OpenMP
- Python中if __name__ == '__main__':作用
- 使用tensorflow查询机器上是否存在可用的gpu设备
- 详解浏览器解析一个URL的全过程
- 深入浅出TCP/UDP 原理-TCP篇(1)及完整MATLAB实现UDP通信
- 人工智能学习实战之路
- 台湾大学林轩田机器学习技法课程学习笔记4 -- Soft-Margin Support Vector Machine
- openCV视频处理与图像转换