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 here
            IndirectRefTable* 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 here
            IndirectRefTable* 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
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值