JNI oracle 详细文档
JNI overview
JNI 知识脉络
JNI design
JNI 暴露给 native 的是一个 Pointer.Java 调用 native 的方法时,这个 Pointer 作为一个参数(即 JNIEnv,提供了很多操作 JVM 的方法,这些方法叫作 JNI 方法)
编译/ 链接 /加载
System.loadLibrary
来加载 native 代码到内存中。JVM 内部为每一个classLoader
维护一个已加载的 native library list.JNI 方法名称解析
Java_切割的全称类名_切割的方法名[__如果方法重载,切割的方法参数签名]
_
/ ;
/ [
等)会进行相应的转义,转义成_0
/_1
/...,因为方法名以及类名等不会以数字为开头(所以 JNI 方法名的_数字
不会有多重含义)。native 方法参数
JNIEnv*
是一个 JVM 指针,提供 JVM 对 native 的各种功能:access 对象,读取 native 调用 JNI 产生的 exception 等。f(JNIEnv*,jobject,args...)
,第二个参数为调用的 classf(JNIEnv*,jobject,args...)
,第二个参数为调用的对象。native 引用 JVM 对象
基本类型数据是直接 copy value 的
其余类型传递引用到 native。所以
对于 native 代码来说,对象的 reference 有两种:global和local refs
globalRef = (*env)->NewGlobalRef(env, localRef)
申请,调用(*env)->DeleteGlobalRef(env, globalRef)
声明不再引用。(*env)->DeleteLocalRef(env, localRef)
来允许 JVM 回收这个对象。native 代码获取对象的 property 和调用对象的 method
jmethodID mid = env->GetMethodID(cls, “f”, “(ILjava/lang/String;)D”);
jdouble result = env->CallDoubleMethod(obj, mid, 10, str);
methodId
后,可以继续继续调用env->CallXXXMethod
来调用,但是如果调用时这个 class 已经被 JVM unload 了,会产生问题。所以最好每次都GetMethodId
后再CallXXXMethod
.错误异常处理
native 代码调用 JNI 方法时,JNI 方法可能会抛出错误。native 代码应该在调用 JNI 方法后,调用ExceptionOccurred()
来检查是不是发生了错误,获取 pendingException,并且做相应的处理。如果没有处理 pendingException 就调用其他的 JNI 方法,即有可能会发生错误,只有少数几个 JNI 方法在有 pendingException 的情况下可以正常运行
native 代码调用 JNI 方法ExceptionClear()
来表示 pendingException 已经被处理了。
native 代码 return 的时候,有未处理的 pendingException,会在 java 代码里 raise this pendingException
native 代码可以调用 JNI 方法ThrowNew(JNIEnv*, exceptionClassObject, exceptionMsgString)
来主动 raise an exception.
ExceptionOccurred()
来获取是否另一个线程有异常发生,这里我不能很好的理解。JNI 数据类型
jobject / jclass / jstring / jarray / jXXXarray / jthrowable
Zboolean Bbyte Cchar Sshort Iint Jlong Ffloat Ddouble
L fully-qualified-class ;
,比如Ljava/lang/String;
[type
,比如[B
( arg-types ) ret-type
,比如 (ILjava/lang/String;[I)J
JNI Functions
JNIEnv*
是一个指针,指向一个包含所有 JNI 方法指针的struct
。Invocation API 。 用来给独立的 native 代码(即不是从 java 的System.loadLibrary
加载的 native 代码)操作 JVM 的 API
可以主动新建一个JVM
;让 JVM 加载一个指定的class
;执行类的某些方法或者进行某些操作(就像一般的 native 代码执行 jvm 方法一样)。
JVM 加载 native library
jint JNI_OnLoad(JavaVM *vm, void *reserved);
在System.loadLibrary
时 JVM 主动调用,以获取 native library 要求的 JVM 版本号(比如JNI_VERSION_1_2
这些都是已定义好的int
常量)。void JNI_OnUnload(JavaVM *vm, void *reserved);
在包含 native library 的 class loader 被 gc 的时候由 JVM 主动调用,以让 native code 执行一些必要的内存清理工作(比如释放 global reference 等)。Invocation API functions
native 可以用来主动操作 JVM 的方法(全局方法,不需要调用env->XXX
,需要在 native code 里#include <jni.h>
)。
jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
native code 调用这个方法来获取 JVM 的默认配置参数,vm_args
是一个指向JavaVMInitArgs
结构的指针。// JavaVMInitArgs结构 typedef struct JavaVMInitArgs { jint version; jint nOptions; JavaVMOption *options; jboolean ignoreUnrecognized; } JavaVMInitArgs; // JavaVMOption typedef struct JavaVMOption { char *optionString; /* the option as a string in the default platform encoding */ void *extraInfo; } JavaVMOption; // JavaVM结构 typedef const struct JNIInvokeInterface *JavaVM; const struct JNIInvokeInterface ... = { NULL, NULL, NULL, DestroyJavaVM, AttachCurrentThread, DetachCurrentThread, GetEnv, AttachCurrentThreadAsDaemon };
从 JDK1.2 开始,不支持一个进程里有多个 JVM 了。所以下面的几个方法都受到影响(比如只能获得一个 JVM,或者是)
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
获取创建的所有 JVM,放到vmBuf
里。jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
新建一个JavaVM
,native code 所在的线程将会是 JVM 的主线程,参数p_env
会用来放 JVM 主线程的JNI Interface
,参数vm_args
是指向JavaVMInitArgs
结构的指针。Java VM
结构方法表中的方法:
jint DestroyJavaVM(JavaVM *vm);
将当前线程 attatch 到 JVM 上,在该线程成为 JVM 的唯一用户线程时,退出该 JVM 并且释放其占有的资源。jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
把当前线程加到 JVM 里,参数p_env
接收加进去以后的JNI Interface
的指针,thr_args
是增加线程到 JVM 的参数,结构如下:typedef struct JavaVMAttachArgs { jint version; /* must be at least JNI_VERSION_1_2 */ 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
jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** p_env, void* args);
把当前线程作为 Daemon thread加到 JVM 里。如果已经加过了,单纯地设置p_env
的值,不会改变已经添加的线程的 daemon 状态(即如果之前不是 daemon,调用这个方法并不会让其变成 daemon)jint DetachCurrentThread(JavaVM *vm);
取消 JVM 里的当前线程,其占有的锁都会释放掉。jint GetEnv(JavaVM *vm, void **env, jint version);
获取 JVM 中当前线程对应的JNI Interface
,version
参数为要求的 JVM version,如果实际的 JVM 不支持指定的 version 的话(比如实际为1.1
要求的却是1.2
),会返回错误,static
代码块中执行System.loadLibrary
(对于 kt 为 companion object 的 init
block).javac
或者kotlinc
(在 AS 的 plugins 中有该工具),生成.class
.javah X.class
对生成的.class
文件,生成所需的 C header file , .h
.h
对应的 .c
文件,在其中实现方法声明的方法。gcc -c X.c
来生成 .o
文件gcc -shared -o X.so X.o
来生成 .so
文件,得到共享库。(在 linux 上为libXXX.so
,在 Mac 上为libXXX.jnilib
)
java
执行有 jni 参与的 java 类。
-Djava.library.path=""
来引用所有的 jni 库-cp
来指定所需的 class 或者 jar.在调用 native
方法前的任何时间都可以.通常在类的 static
代码块中进行加载.
javah
生成 .h
文件,实现其中的方法. 优点: 简单; 缺点 : 方法名长.jNI_OnLoad
方法中调用 JNIEnv.registerNatives
来进行注册,其中参数有 java 方法名
和 c 方法指针
的对应.jobject
.jniEnv.getMethodId(jclass, methodName, methodSig)
获取 jMethodID -> 通过 jniEnv.callVoidMethod(obj, methodId, params)
;static
方法要使用 jniEnv.callStaticVoidMethod
(可能是因为涉及到方法的分派)getByyteField
等方法来获取.