概念:JNI(Java Native Interface)java本地化接口,狭义上是SUN定义的一套标准接口,广义上是标准接口、结构体、符号常量等的集合。
作用:实现 java代码 和 本地代码(以C/C++为主)的交互,包括数据类型的相互转换、互调彼此的方法。
实现形式:JNI的具体实现是由本地语言编写的数据类型、常量、函数、类、结构体等,是JDK中的一揽子本地文件,其中比较有代表性的jni.h及jni.cpp在OpenJDK的\openjdk\hotspot\src\share\vm\prims路径下,java程序启动时,JNI的代码被加载到java虚拟机中。
使用位置:在本地代码中使用,通过引用jni.h即可使用JNI定义的数据类型、方法、结构体等;利用JNI中定义的JNIEnv结构体,可通过函数指针调用JNI的函数。
(注意:JNI是java的,NDK是Android的)
效率:假设java调用java方法时间是1,java通过JNI调用本地方法时间约为3,本地通过JNI调用Java方法的时间大约是10。
层次结构:为便于理解,用三层来进一步说明JNI的作用,其中本地层指C/C++的源码或库,java本身无法调用本地层代码,需要在本地层外面用C/C++语言并按照JNI标准,封装一层JNI,虽然JNI使用C/C++编写,但JNI函数不同于本地层函数,具体不同在于方法参数(JNI函数必须含有JNIEnv*和jobject/jclass两个类型的形参)、数据类型、命名上,后面会详细说明,这些不同使得java层可以和JNI层相互调用,而由于JNI是用C/C++实现的,故JNI层可以与本地层相互调用,这就间接实现了java与本地层的相互调用。其中java层调用JNI层,需要先建立映射再函数查找,JNI层调用java层直接通过反射机制。
JNI定义了java的数据类型与C/C++数据类型的对应关系,基本类型定义如下:
java基本数据类型 | JNI定义的在C/C++中对应的基本类型 | JNI起的别名(typedef) |
short | short(有符号16位) | jshort |
int | int(有符号32位) | jint |
long | long long(有符号64位) | jlong |
float | float(有符号32位) | jfloat |
double | double(有符号64位) | jdouble |
char | unsigned short(无符号16位) | jchar |
boolean | unsigned char(无符号8位) | jboolean |
byte | signed char(有符号8位) | jbyte |
注意:
(1)java层的 true 和 false 转化为本地层后,会用1和0表示;而本地层的 非零值 和 0 转化到本地层后,会用java层的true和fasle表示。
(2)JNI层的 jboolean 实际上是本地层的unsigned char,故可对jboolean 类型的变量赋予整形常量(不超过255)
(3)本地层C++也有true和false关键字,不同于java的true和false,前者实际上代表数值1和0,可直接赋值给C++的其他基本类型变量,如int类型变量;而对于java的true和false,不是数值,只能赋值给boolean类型变量。
由上表可知,JNI用C/C++的部分基本类型来对应java的基本类型,那么JNI把java的引用类型对应成C/C++的啥类型呢?请看下表:
java的引用类型 | JNI定义的在C++中对应的指针类型 | JNI起的别名(typedef) |
Object类及其子类 |
class _jobject {}; _jobject* |
jobject、jweak(JNI多定义的弱全局引用) |
Class类 | class _jclass : public _jobject {}; _jclass* |
jclass |
String类 | class _jstring : public _jobject {}; _jstring* |
jstring |
class _jarray : public _jobject {}; | jarray(JNI数组类型的父类) | |
Object[ ] | class _jobjectArray : public _jarray {}; _jobjectArray* |
jobjectArray |
boolean[ ] | class _jbooleanArray : public _jarray {}; _jbooleanArray* |
jbooleanArray |
byte[ ] | class _jbyteArray : public _jarray {}; _jbyteArray* |
jbyteArray |
chart[ ] | class _jcharArray : public _jarray {}; _jcharArray* |
jcharArray |
short[ ] | class _jshortArray : public _jarray {}; _jshortArray* |
jshortArray |
int[ ] | class _jintArray : public _jarray {}; _jintArray* |
jintArray |
long[ ] | class _jlongArray : public _jarray {}; _jlongArray* |
jlongArray |
float[ ] | class _jfloatArray : public _jarray {}; _jfloatArray* |
jfloatArray |
double[ ] | class _jdoubleArray : public _jarray {}; _jdoubleArray* |
jdoubleArray |
Throwable[ ] | class _jthrowable : public _jobject {}; _jthrowable* |
jthrowable |
由上表可知,JNI用C++的指针类型来对应java的引用类型,JNI定义了C++的类型来对应Java中比较常用的类,并用C++相应类的指针类型,对应java常用类的引用类型;值得注意的是,JNI将java中的数组引用类型也对应成C++中的指针类型。
JNI定义的引用类型其本质是C++中的指针类型,这里我们称之为JNI中的引用类型,那么由JNI引用类型定义的变量的我们称之为JNI引用变量,JNI引用变量的值我们称之为JNI引用,JNI引用的种类如下:
JNI引用种类 | 生命周期 | 创建 | 释放 | 特点 |
局部引用(LocalRef) | 仅在当前线程当前方法中有效 | 在本地方法中,若无特殊处理,创建的引用都是局部引用 | 本地方法结束后,JavaVM自动释放;也可通过JNI方法DeleteLocalRef手动释放 | 所引用的对象不会被GC; |
全局引用(GlobalRef) | 多线程、多方法中有效 | 需要通过JNI方法NewGlobalRef并基于局部引用手动创建 | 需要通过JNI方法DeleteGlobalRef手动释放 | 所引用的对象不会被GC; |
弱全局引用(GlobalWeakRef) | 多线程、多方法中有效 | 需要通过JNI方法NewGlobalWeakRef并基于局部引用手动创建 | 需要通过JNI方法DeleteGlobalWeakRef手动释放 | 所引用的对象不保证不被GC; |
(注意:对于LocalRef,每个JavaVM有一个LocalRefTable(局部引用表),最大可存储512个局部引用,如果局部引用超过512将报错,导致程序崩溃,如果本地方法在结束前会创建大量局部引用,一定要手动释放!!)
(注意:局部引用、全局引用和局部引用变量、全局引用变量得定义方式是不同的,局部引用变量、全局引用变量是看变量是定义在函数内还是函数外)
(注意:一般不使用GlobalWeakRef!)
(注意:不要试图将局部引用赋值给全局引用变量,即不要缓存局部引用,若需缓存,请将局部引用转为全局引用并赋值给全局引用变量;也不要将全局引用赋值给局部引用变量,这没有意义。)
(注意:一般不使用GlobalWeakRef!)
JNI除了定义了与java数据类型对应的数据类型之外,还定义了一些自己的数据类型,都定义在jni.h头文件中,具体如下:
定义:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
说明:_jfieldID、_jmethodID是结构体类型, jfieldID、jmethodID是指向相应结构体的指针变量,通常只会用到jfieldID、jmethodID,jni.h中隐藏了_jfieldID、_jmethodID的实现细节。
作用:jfieldID用于本地调用Java类或对象的属性时,jmethodID用于本地调用Java类或对象的方法时。
定义:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
struct JNINativeInterface{
jint (*GetVersion)(JNIEnv *);
........
}
struct _JNIEnv {
const struct JNINativeInterface* functions;
jint GetVersion() { return functions->GetVersion(this);
.......
}
说明:JNINativeInterface 结构体中定义了JNI标准的全部方法,_JNIEnv 结构体采用修饰模式,内部持有JNINativeInterface结构体实例的指针,包装了JNINativeInterface结构体的方法。在C++中JNIEnv是_JNIEnv 结构体类型的别名,在C中JNIEnv是 JNINativeInterface* 指针类型的别名,一般我们直接使用JNIEnv,后面我们统一用JNIEnv描述。
作用:JNI的大部分方法都需通过JNIEnv实例调用。
(注意:JNIEnv的实例是与线程相关的,每个线程都有一个,jni可能有多个JNIEnv,不同线程的JNIEnv实例彼此独立,线程间不能共享JNIEnv实例,所以不要尝试将JNIEnv实例缓存为全局的)
(注意:在C++与C中使用JNIEnv指针变量调用JNI方法时,使用方式稍有差别,后面会详细说)
定义:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};
struct _JavaVM {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
说明:JNIInvokeInterface 结构体中定义了本地代码操作JavaVM及获取JNIEnv的方法,_JavaVM 结构体采用修饰模式,内部持有JNIInvokeInterface 结构体实例的指针,包装了JNIInvokeInterface 结构体的方法。在C++中JavaVM是_JavaVM 结构体类型的别名,在C中JavaVM是 JNIInvokeInterface* 指针类型的别名,一般我们直接使用JavaVM,后面我们统一用JavaVM描述。
作用:用于获取JavaVM实例,通过JavaVM实例可获取跟本地线程相关的JNIEnv实例,从而让在本地开启的线程在没有java native方法传递来的JNIEnv实例的情况下 能获取JNIEnv实例调用java方法。
(注意:JavaVM是java虚拟机在JNI层的代表,在一个java进程中只有一个JavaVM实例,因此该进程的所有线程都可以使用这个JavaVM实例,故可考虑将JavaVM缓存为全局的)。
(注意:JavaVM结构体的方法指针指向java invocation API,Invocation API用于把JVM嵌入到本地程序中)
定义:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
说明:变量name是Java中函数的名字;变量signature是Java方法通过描述符表示的方法签名
;变量fnPtr是函数指针,指向本地函数,使用时前面都要接加(void *),其name与fnPtr是对应的,一个是java层方法名,一个是native方法名。
作用:用于JNI动态注册(后面会说),每个JNINativeMethod实例都表示一个本地方法与java方法的映射关系。
JNI定义描述符是为了让本地代码通过JNI调用java时,准确找到Java的类或方法,JNI描述符如下:
java元素 | JNI描述符 |
int | I |
long | J |
byte | B |
short | S |
char | C |
float | F |
double | D |
boolean | Z |
void | V |
引用类型 | L+全类名; |
类型[] | [类型 |
方法 | (形参类型)返回值类型 |
例子:
String[] | [Ljava/lang/String; |
int[][] | [[I |
long f (int n, String s, int[] arr); | (ILjava/lang/String;[I)J |
(注意:引用类型是L+全类名;,并将全类名中的“.” 换成“/”,在最后加上;)
定义:
#define JNIEXPORT __attribute__ ((visibility ("default")))
作用:用于表明这个函数是一个可导出函数。每一个C/C++库都有一个导出函数列表,只有在这个列表里面的函数才可以被外部直接调用,类似Java的public函数和private函数的区别。定义JNI函数时可以不加,默认可以导出。
(注意:也可在JNI函数前直接加__attribute__ ((visibility ("default"))),若将“default”改成“hidden”,则外界无法找到该函数)
定义:
#define JNICALL
作用:只用于说明说明这个函数是一个JNI函数,用来和普通的C/C++函数进行区别,定义JNI函数时可以不加。
定义:
#define JNI_FALSE 0
#define JNI_TRUE 1
作用:JNI定义的字符常量,用来在本地代码中表示真假。
JNI定义的接口全在jni.h中,根据接口的实现方式不同可分为JNI自己实现的、间接调用java invocation API的、.间接调用so的,java invocation API及so为JNI暴露一些有用的方法。以下为方法简介,后续会结合使用详细说明:
接口 | 位置 | 实现 | 作用 |
JNI定义的接口 | JNINativeInterface结构体中的接口 | JNI自己 | 本地代码与java代码交互 |
JNIInvokeInterface结构体中的接口及JNI_CreateJavaVM、JNI_GetCreatedJavaVMs、JNI_GetDefaultJavaVMInitArgs | 调用java invocation API相关的方法 | 用于将JVM嵌入到本地程序中 | |
JNI_OnLoad JNI_OnUnload |
调用.so库相关方法 | java加载、卸载so库时,会在JNI层回调,可用于JNI动态注册、持久化JavaVM等 |
好了分享就到这里,如有问题请告知,希望大家点个赞支持一下!!!