在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实现源码分析【三 间接引用表】相关推荐

  1. Nouveau源码分析(三):NVIDIA设备初始化之nouveau_drm_probe

    Nouveau源码分析(三) 向DRM注册了Nouveau驱动之后,内核中的PCI模块就会扫描所有没有对应驱动的设备,然后和nouveau_drm_pci_table对照. 对于匹配的设备,PCI模块 ...

  2. 【投屏】Scrcpy源码分析三(Client篇-投屏阶段)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  3. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  4. 【转】ABP源码分析三十五:ABP中动态WebAPI原理解析

    动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...

  5. 【转】ABP源码分析三十三:ABP.Web

    ABP.Web模块并不复杂,主要完成ABP系统的初始化和一些基础功能的实现. AbpWebApplication : 继承自ASP.Net的HttpApplication类,主要完成下面三件事 一,在 ...

  6. 【转】ABP源码分析三:ABP Module

    Abp是基于模块化设计思想进行构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modul ...

  7. ABP源码分析三十四:ABP.Web.Mvc

    ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...

  8. 【转】ABP源码分析三十四:ABP.Web.Mvc

    ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...

  9. 【转】ABP源码分析三十一:ABP.AutoMapper

    这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...

最新文章

  1. 10 款 VS Code 插件神器,第 7 款超级实用!
  2. 如何在算法交易中使用AI?摩根大通发布新版指南
  3. 关于深度学习的小知识点
  4. pymysql.err.InterfaceError: (0, '')
  5. C# linq创建嵌套组
  6. hashMap与hashTable区别
  7. 【机器学习基础】如何在Python中处理不平衡数据
  8. springquartz的LocalDataSourceJobStore
  9. 6个很棒的PostCSS插件,让您成为一个CSS向导
  10. linux arm mmu基础【转】
  11. 嵌入式操作系统内核原理和开发(地址空间)
  12. Leetcode 5182.删除一次得到子数组最大和
  13. Ant之build.xml详解
  14. Kubernetes架构图 Architecture Workload Networking Storage RBAC
  15. linux rpm解压命令,LINUX下各种解压命令收藏
  16. 3dsMax批量设置可编辑样条线的视口渲染
  17. 根据ip地址定位城市信息
  18. 学习verilog的经典好教材与资料
  19. 华为服务器linux光驱名称,华为服务器通过mgmt口挂载光盘装系统及Linux系统rescue模式下修复内核...
  20. 仿原生安卓文件管理器

热门文章

  1. PROTEUS串口仿真遇到的BUG(转载)
  2. MOSS点滴(1):如何开发和部署feature
  3. 高性能计算的线程模型:Pthreads 还是 OpenMP
  4. Python中if __name__ == '__main__':作用
  5. 使用tensorflow查询机器上是否存在可用的gpu设备
  6. 详解浏览器解析一个URL的全过程
  7. 深入浅出TCP/UDP 原理-TCP篇(1)及完整MATLAB实现UDP通信
  8. 人工智能学习实战之路
  9. 台湾大学林轩田机器学习技法课程学习笔记4 -- Soft-Margin Support Vector Machine
  10. openCV视频处理与图像转换