JNI是如何注册本地方法的
在开发android 的时候,我们总是会用的jni技术,我有一种好奇,本地方方法是如何和JVM关联起来的。
1,
jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
JNIEnv* env = NULL;
if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGD(">>>Get env faild!!!");
return -1;
}
jclass clazz = (env)->FindClass("com/example/hellojni/HelloJni");
if (!clazz) {
LOGE(">>>>Can't find com/example/hellojni/HelloJn class!!! ");
}
(env)->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));
LOGD(">>>>JNI_OnLoad Called!!!");
return JNI_VERSION_1_4;
}
通过前面的分析,我们在写JNI的时候,通常会这样实现一个JNI_OnLoad方法,为什么要这样做呢,因为方便。
(env)->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])); 这个调用就是注册本地方法,
我们寻找到jni.cpp 去看看这个 RegisterNatives是如何实现的。
2,
这里我们看到
通过前面的分析,我们在写JNI的时候,通常会这样实现一个JNI_OnLoad方法,为什么要这样做呢,因为方便。
(env)->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])); 这个调用就是注册本地方法,
我们寻找到jni.cpp 去看看这个 RegisterNatives是如何实现的。
2,
这里我们看到
ScopedJniThreadState ts(env);
ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);
dvmRegisterJNIMethod(clazz, methods[i].name, methods[i].signature, methods[i].fnPtr)
这样3行代码, ScopedJniThreadState 这行代码我没有看懂什么意思,大概是每次JNI调用都必须调用,我想可能是同步锁还是什么的,因为所有的JNI函数开头都调用这个函数。
(ClassObject*) dvmDecodeIndirectRef(env, jclazz); 这个把java层的引用映射到对应的表中,
先前我们谈到我们在C++层new对象的时候,可以new3种对象,一种是全局的,一种是全局的弱引用,第三种是local变量,我们知道jclazz是java层传入的拥有本地方法的对象的引用。
a,看到Object* dvmDecodeIndirectRef(JNIEnv* env, jobject jobj)的实现,我们猜想,java编译器可能根据java代码的实现在分配内存的时候,引用有所归类。
因为区分当前引用的函数实现如下
这样3行代码, ScopedJniThreadState 这行代码我没有看懂什么意思,大概是每次JNI调用都必须调用,我想可能是同步锁还是什么的,因为所有的JNI函数开头都调用这个函数。
(ClassObject*) dvmDecodeIndirectRef(env, jclazz); 这个把java层的引用映射到对应的表中,
先前我们谈到我们在C++层new对象的时候,可以new3种对象,一种是全局的,一种是全局的弱引用,第三种是local变量,我们知道jclazz是java层传入的拥有本地方法的对象的引用。
a,看到Object* dvmDecodeIndirectRef(JNIEnv* env, jobject jobj)的实现,我们猜想,java编译器可能根据java代码的实现在分配内存的时候,引用有所归类。
因为区分当前引用的函数实现如下
/*
* Determine what kind of indirect reference this is.
*/
INLINE IndirectRefKind indirectRefKind(IndirectRef iref)
{
return (IndirectRefKind)((u4) iref & 0x03);
};
这个明显是根据指针的最后2bit位来区别的。具体的实现,我没有研究,
但是我们很有兴趣的知道,这个3种引用是如何被映射的。
这个明显是根据指针的最后2bit位来区别的。具体的实现,我没有研究,
但是我们很有兴趣的知道,这个3种引用是如何被映射的。
case kIndirectKindLocal:
{
Object* result = getLocalRefTable(env)->get(jobj);
}
local 方法是根据env来 获得 的,先前我们又说,env是根据线程来的,dvm每创建一个线程就会创建一个和当前线程对应的环境env。同时env中有一个指针指向了当前的线程。同时当前线程有一个成员变量jniLocalRefTable。这个就是存放当前线程的local变量引用,当然了在C++中叫指针。这个表最大好像是512,如果我们线程创建了太多的local变量,但是却没有解引用就会引发异常。
b,全局引用和全局弱引用,都是存放在gDvm这个全局变量你们的,这里维护了2张表,jniGlobalRefTable和jniWeakGlobalRefTable; gDvm是我们在创建dvm的时候创建的,如果有不明白的朋友,可以看我先前的文章。有分析dvm是如何创建的。
3,我们来看static bool dvmRegisterJNIMethod 方法的实现。这个是我们想dvm注册本地方法。
他调用了dvmUseJNIBridge(method, fnPtr);他有2个参数,第一个是我们根据类名,方法名,和签名获得的Method 指针。第二个参数是是我们需要执行的本地方法。
这里有个func 桥,dvmCallJNIMethod,这个是所有JNI函数调用方法,这个函数我没有深入研究。
至此,我们知道了,其实所谓的本地方法,其实就是一种映射,不过你们有很多东西值得我们研究。其实对我们开发有用的东西主要是我们知道了,我们本地方法new的对象,分为3种,同时有数量的限制。所以当我们在主线程下new对象的时候,我们有必要及时的销毁引用,以避免引用太多异常的抛出。如果是创建其他线程,我们也要注意new 的对象数量的限制。这里我们有必要提醒刚做JNI开发的朋友,这里说的new是说通过调用NewGlobalRef 之类所建立的对象。
今天就分析到这里。
}
local 方法是根据env来 获得 的,先前我们又说,env是根据线程来的,dvm每创建一个线程就会创建一个和当前线程对应的环境env。同时env中有一个指针指向了当前的线程。同时当前线程有一个成员变量jniLocalRefTable。这个就是存放当前线程的local变量引用,当然了在C++中叫指针。这个表最大好像是512,如果我们线程创建了太多的local变量,但是却没有解引用就会引发异常。
b,全局引用和全局弱引用,都是存放在gDvm这个全局变量你们的,这里维护了2张表,jniGlobalRefTable和jniWeakGlobalRefTable; gDvm是我们在创建dvm的时候创建的,如果有不明白的朋友,可以看我先前的文章。有分析dvm是如何创建的。
3,我们来看static bool dvmRegisterJNIMethod 方法的实现。这个是我们想dvm注册本地方法。
他调用了dvmUseJNIBridge(method, fnPtr);他有2个参数,第一个是我们根据类名,方法名,和签名获得的Method 指针。第二个参数是是我们需要执行的本地方法。
这里有个func 桥,dvmCallJNIMethod,这个是所有JNI函数调用方法,这个函数我没有深入研究。
至此,我们知道了,其实所谓的本地方法,其实就是一种映射,不过你们有很多东西值得我们研究。其实对我们开发有用的东西主要是我们知道了,我们本地方法new的对象,分为3种,同时有数量的限制。所以当我们在主线程下new对象的时候,我们有必要及时的销毁引用,以避免引用太多异常的抛出。如果是创建其他线程,我们也要注意new 的对象数量的限制。这里我们有必要提醒刚做JNI开发的朋友,这里说的new是说通过调用NewGlobalRef 之类所建立的对象。
今天就分析到这里。

浙公网安备 33010602011771号