本文是对链接http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html的学习笔记,限于英文水平和对JNI的理解,可能存在错误。
通过使用Invocation API,使用C/C++开发的本地应用可以访问Java虚拟机提供的特性。为了描述简单,下面提到的VM指的都是Java虚拟机。
在本地应用里,调用JNI_CreateJavaVM()方法可以完成初始化、加载VM,并返回指向新VM对象的一个指针。调用JNI_CreateJavaVM方法的线程,被称为主线程。
JNIEnv对象并不是线程安全的,因此只能在当前线程使用。当需要跨线程使用JNIEnv对象时,需要通过调用AttachCurrentThread方法将当前线程与JVM进行关联,并得到一个指向JNIEnv对象的指针。当AttachCurrentThread方法调用成功之后,当前本地线程即可被VM感知。由于本地线程并不由JVM创建,因而需要确保自身有足够的栈空间来执行必要的代码。
调用者可以在本地线程中调用DetachCurrentThread来解除关联关系,以便于释放资源,否则会导致资源泄漏;但在本地线程的调用栈内仍有Java方法时,调用DetachCurrentThread方法可能会失败。
当VM使用完毕,就应当考虑停止VM并回收资源,通过调用JNI_DestroyJavaVM方法即可达到这一目的。在VM看来,用户线程包括VM在执行Java字节码时创建的Java线程,以及通过调用AttachCurrentThread方法进而与VM完成关联的本地线程。用户线程的代码在执行时,可能会持有比如锁、窗口之类的系统资源,为了简化VM的实现,VM把释放资源的操作留给程序员去做,VM要求调用JNI_DestroyJavaVM方法的当前线程必须是当前唯一存活的用户线程,否则JNI_DestroyJavaVM方法调用后可能无法达到预期的效果。
本地动态库被VM加载之后,对于VM内部所有的类加载器都是可见的。即VM内部由不同类加载器加载的两个类可以关联到相同的本地方法,这带来两个问题:
为了解决上述的两个问题,引入了新的解决方法,即某个类加载器自己管理当前加载的本地库的集合,并且相同的本地库只能被一个类加载器加载。应用的代码违反这两点约束将导致UnsatisfiedLinkError的出现。
新方法的优点有:
为了便于实现上述特性,VM暴露了方法JNI_OnLoad。在VM加载本地库时,VM会自动在本地库文件中查找这个方法,如果方法存在则通过这个方法来获取本地库使用的JNI版本号,这样VM可以决定本地库使用VM特性的请求是否合理。如果JNI_OnLoad方法没有实现,VM认为本地库基于JNI_VERSION_1_1相关的特性实现。如果VM无法识别JNI_OnLoad方法的返回值,VM会忽略本地库的加载请求,并清理现场。
当加载本地库的类加载器被GC之后,VM会主动调用本地库导出的JNI_OnUnLoad方法,如果本地库没定义这个方法的话,这个步骤将自动忽略。一般而言,可以在JNI_OnUnLoad方法内部做一些清理操作。由于JNI_OnUnLoad方法被VM回调的时机不确定,因而要避免在这个方法内部调用Java语言的方法以及VM提供的特性。
这一章节提到的API均由VM提供。方法的返回值为JNI_OK 时表示调用成功,非JNI_OK 表示调用失败。
如下是常用的几个结构定义。
typedef struct JavaVMInitArgs { jint version; jint nOptions; JavaVMOption *options; jboolean ignoreUnrecognized; } JavaVMInitArgs; typedef struct JavaVMOption { char *optionString; /* the option as a string in the default platform encoding */ void *extraInfo; } JavaVMOption; typedef struct JavaVMAttachArgs { jint version; char *name; /* the name of the thread as a modified UTF-8 string, or NULL */ jobject group; /* global ref of a ThreadGroup object, or NULL */ } JavaVMAttachArgs;
通过调用本方法,可以卸载已创建的VM对象,并回收相关的资源。本方法是线程安全的,可以在任意线程使用。本方法在使用时会阻塞当前线程吗?假如调用线程没有与VM对象建立了关联关系,则直接建立关联关系,然后等待其它用户线程退出;假如已建立了关联关系,则直接等待其它用户线程退出。
Unloading of the VM is not supported.这语没有看明白什么意思。
调用本方法时,第一个参数为指向VM对象的指针,第二个参数为JNIEnv类型对象的指针,第三个参数为JavaVMAttachArgs类型对象的指针,但没有实际用途,应当为设置为NULL(不过原文档对这个参数的介绍稍有点混乱,需要实际验证一下)。
用法和参数与AttachCurrentThread方法类似,区别在于VM内部创建的java.lang.Thread对象将会是一个daemon。如果当前本地线程已经和VM建立关联,则多次调用AttachCurrentThread或者AttachCurrentThreadAsDaemon并不会修改java.lang.Thread对象的daemon属性。
当前线程与VM解除关联,本地线程持有的锁对象将全部释放。等待当前线程的Java线程将得到通知。主线程通过调用本方法,可以和VM解除关联。
通过调用本方法,可以获取到当前线程的JNIEnv对象。如果当前线程与VM没有建立关联,则*env被设置为NULL,同时返回JNI_EDETACHED;如果传入的VM特性版本号不被支持,则*env被设置为NULL,同时返回JNI_EVERSION;如果调用成功,则*env被设置为正确的JNIEnv对象指针,同时返回JNI_OK。
JNI学习之Invocation API,布布扣,bubuko.com
原文地址:http://blog.csdn.net/jackie_xiaonan/article/details/24922299