第40篇-JNIEnv和JavaVM
下面介绍2个与JNI机制相关的类型JNIEnv和JavaVM。
1、JNIEnv
JNIEnv一般是是由虚拟机传入,而且与线程相关的变量,也就说线程A不能使用线程B的JNIEnv。而作为一个结构体,它里面定义了JNI系统操作函数
。在之前介绍的实例中,可以看到C的Java_TestJNI_set()或Java_TestJNI_get()函数的实现中,第1个参数的类型为JNIEnv*。JNIEnv的定义如下:
来源:openjdk/hotspot/src/share/vm/prims/jni.h struct JNIEnv_; #ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
JNIEnv在C语言环境和C++语言环境中的实现是不一样。在C中定义为JNINativeInterface_,在C++中定义为JNIEnv_。
JNIEnv_结构体的定义如下:
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
#ifdef __cplusplus
jint GetVersion() {
return functions->GetVersion(this);
}
jclass DefineClass(const char *name, jobject loader, const jbyte *buf,jsize len) {
return functions->DefineClass(this, name, loader, buf, len);
}
jclass FindClass(const char *name) {
return functions->FindClass(this, name);
}
...
#endif
}
在JNIEnv_中定义了一个functions变量,这个变量是指向JNINativeInterface_的指针。所以如果我们在写native函数时,当接收到类型为JNIEnv*的变量env时,可以使用如下方式调用JNIEnv中的函数(准确说是通过函数指针来调用函数,因为JNIEnv
的数据结构聚合了所有 JNI 函数的函数指针),我们可在C++中通过如下方式调用:
env->FindClass("java/lang/String") // C++中的写法
而在C中可以通过如下方式调用:
(*env)->FindClass(env, "java/lang/String") // C中的写法
而由于变量functions是定义在结构体JNIEnv_的第1个变量,所以我们通过*env就能获取到functions变量的值,然后通过JNINativeInterface中的函数指针来调用对应的函数。
JNINativeInterface_结构体的定义如下:
struct JNINativeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2;
void *reserved3; jint (JNICALL *GetVersion) (JNIEnv *env); jclass (JNICALL *DefineClass) (JNIEnv *env, const char *name, jobject loader, const jbyte *buf,jsize len);
jclass (JNICALL *FindClass) (JNIEnv *env, const char *name);
// ...
}
下面就来介绍一下JNIEnv_结构体中保存的函数指针对应的函数,如下:
(1)jclass类型表示Java中的Class类。JNIEnv_结构体中有如下几个简单的函数(其实是通过函数指针调用对应的函数,我们直接表达为函数,后面也采用类似的表达方式)可以取得jclass:
- jclass FindClass(const char* clsName):通过类的名称(类的全名,这时候包名不是用.号,而是用/来区分的)来获取jclass,如:jclass str = env->FindClass("java/lang/String");获取Java中的String对象的class实例;
- jclass GetObjectClass(jobject obj):通过对象实例来获取jclass,相当于Java中的getClass()方法;
- jclass GetSuperClass(jclass obj):通过jclass可以获取其父类的jclass实例。
(2)引用相关的API
- jobject NewGlobalRef(JNIEnv *env, jobject obj):创建一个指向obj的全局引用,obj可以是本地或者全局引用,全局引用只能通过显示调用DeleteGlobalRef()释放;
- void DeleteGlobalRef(JNIEnv *env, jobject globalRef):删除一个全局引用;
- jobject NewLocalRef(JNIEnv *env, jobject ref):创建一个指向实例ref的本地引用;
- void DeleteLocalRef(JNIEnv *env, jobject localRef):删除一个本地引用;
- jint EnsureLocalCapacity(JNIEnv *env, jint capacity):评估当前线程是否能够创建指定数量的本地引用,如果可以返回0,否则返回负数并抛出OutOfMemoryError异常。在执行本地方法前JVM会自动评估当前线程能否创建至少16个本地引用。JVM允许创建超过评估数量的本地引用,如果创建过多导致JVM内存不足JVM会抛出一个FatalError;
- jint PushLocalFrame(JNIEnv *env, jint capacity):创建一个新的支持创建给定数量的本地引用的Frame,如果可以返回0,否则返回负数并抛出OutOfMemoryError异常。注意在之前的Frame中创建的本地引用在新的Frame中依然有效
- jobject PopLocalFrame(JNIEnv *env, jobject result):弹出掉当前的本地引用Frame,然后释放其中的所有本地引用,如果result不为NULL,则返回该对象在前一个即当前Frame之前被push的Frame中的本地引用。PushLocalFrame和PopLocalFrame两个都是配合使用,常见于方法执行过程中产生的本地引用需要尽快释放掉;
- jweak NewWeakGlobalRef(JNIEnv *env, jobject obj):创建一个指向对象obj的弱全局引用,jweak是jobject的别名,如果obj是null则返回NULL,如果内存不足则抛出OutOfMemoryError异常;
- void DeleteWeakGlobalRef(JNIEnv *env, jweak obj):删除弱全局引用;
- jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj):获取某个对象引用的引用类型,JDK1.6引入的。
(3)获取jfieldID和jmethodID
在C/C++本地代码中访问Java端的代码,一个常见的应用就是获取类的字段和调用类的方法,为了在C/C++中表示字段和方法,JNI在jni.h头文件中定义了jfieldId,jmethodID类型来分别代表Java端的字段和方法
我们在访问或者设置Java字段的时候,首先就要先在本地代码取得代表该Java字段的jfieldID,然后才能在本地代码中进行Java属性操作。同样在调用Java端的方法时,也需要取得代表该方法的jmethodID才能进行Java方法调用。相关的函数如下:
- jfieldID GetFieldID(jclass clazz,const char* name,const char* sign),获取实例字段的jfieldID;
- jfieldID GetStaticFieldID(jclass clazz, const char *name,const char *sig),获取静态字段的jfieldID;
- jmethodID GetMethodID(jclass clazz, const char *name,const char *sig),获取实例方法的jmethodID;
- jmethodID GetStaticMethodID(jclass clazz, const char *name,const char *sig),获取静态方法的jmethodID。
更多关于JNI函数可参数:JNI Functions
2、加载和卸载本地方法
在编写JNI对应的C/C++语言的实现时,还可以实现如下函数:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved); JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved);
JVM提供了一种方式允许你在加载动态链接库文件的时候做一些你想做的事情,也就是JNI_OnLoad()函数。显式的卸载一个本地库时会看到JNI_OnUnload()函数被调用。JNI_OnLoad()函数是在动态库被加载时调用,而JNI_OnUnload()函数则是在本地库被卸载时调用。所以这两个函数就是一个本地库最重要的两个管理生命周期的函数。
可以用这一组函数实现Java方法和本地C函数的链接。举个例子如下:
package com.classloading;
class NativeLib{
public static native String getName(int number);
}
按照约定,需要在本地文件中有如下声明:
JNIEXPORT jstring JNICALL Java_com_classloading_NativeLib_getName(JNIEnv *env,jobject thiz,jint number);
不过现在我们声明了一个没有按照JNI规范命名的本地函数,如下:
JNIEXPORT jstring JNICALL getName(JNIEnv *env, jclass clazz);
必须使用动态关联的方式实现Java方法与本地函数的映射,代码如下:
extern "C"
JNIEXPORT jstring JNICALL getName(JNIEnv *env, jobject thiz, int number) {
printf("number is %d",number);
return env->NewStringUTF("hello world");
} static const char *CLASS_NAME = "com/classloading/NativeLib"; // 类名 static JNINativeMethod method = { // 本地方法描述
"getName", // Java方法名
"(I)Ljava/lang/String;", // Java方法签名
(void *) getName // 绑定到对应的本地函数
}; static bool bindNative(JNIEnv *env) {
jclass clazz;
clazz = env->FindClass(CLASS_NAME);
if (clazz == NULL) {
return false;
}
return env->RegisterNatives(clazz, &method, 1) == 0;
} JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return result;
} bool res = bindNative(env);
printf("bind result is %s",res?"ok":"error"); // 返回JNI的版本
return JNI_VERSION_1_6;
}
注意:JNI_OnLoad()函数在每一个动态链接库中只能存在一个。常用javah去生成JNI的头文件,然后严格按照头文件中声明的函数名称等去实现自己的JNI函数,使用这种方式比较传统,定义的格式甚至连名字都必须按照规范,不过JVM也同时提供了RegisterNative()函数手动的注册native方法。调用的RegisterNatives()函数声明如下:
jint RegisterNatives(jclass clazz, const JNINativeMethod *methods,jint nMethods)
我们现在规范一下术语:
函数的解析如下:
1、methods 是一个二维数组(method需要取地址后就相当于二维数组),代表着这个clazz里的每一个native方法所对应的实现函数,在下面的例子中表示,一个native 方法retrieveDirectives,返回值为AssertionStatusDirectives,所对应的执行的本地函数是JVM_AssertionStatusDirectives。实例如下:
来源:/openjdk/jdk/src/share/native/java/lang/ClassLoader.c static JNINativeMethod methods[] = {
{"retrieveDirectives", "()Ljava/lang/AssertionStatusDirectives;", (void *)&JVM_AssertionStatusDirectives}
}; JNIEXPORT void JNICALL Java_java_lang_ClassLoader_registerNatives(JNIEnv *env, jclass cls) {
(*env)->RegisterNatives(env, cls, methods,
sizeof(methods)/sizeof(JNINativeMethod));
}
2、后面的nMethods代表要指定的native方法的数量
最后就是卸载native方法了,实现代码如下:
static bool unBindNative(JNIEnv *env) {
jclass clazz;
clazz = env->FindClass(CLASS_NAME);
if (clazz == NULL) {
return false;
}
return env->UnregisterNatives(clazz) == 0;
} JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
bool res = unBindNative(env);
sprintf("unbind result is %s", res ? "ok" : "error");
}
3、JavaVM
JavaVM是虚拟机在JNI中的表示,一个JVM中只有一个JavaVM实例,这个实例是线程共享的。通过JNIEnv可以获取一个Java虚拟机实例,其函数如下:
jint GetJavaVM(JNIEnv *env, JavaVM **vm);
vm用来存放获得的虚拟机指针的指针,成功返回0,失败返回其它值。
struct JNIInvokeInterface_; struct JavaVM_; #ifdef __cplusplus
typedef JavaVM_ JavaVM;
#else
typedef const struct JNIInvokeInterface_ *JavaVM;
#endif
可以看到C语言环境和C++语言环境中的实现是不一样。JNIInvokeInterface_与JavaVM_的定义如下:
struct JNIInvokeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2; jint (JNICALL *DestroyJavaVM)(JavaVM *vm); jint (JNICALL *AttachCurrentThread)(JavaVM *vm, void **penv, void *args); jint (JNICALL *DetachCurrentThread)(JavaVM *vm); jint (JNICALL *GetEnv)(JavaVM *vm, void **penv, jint version); jint (JNICALL *AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args);
}; struct JavaVM_ {
const struct JNIInvokeInterface_ *functions;
#ifdef __cplusplus jint DestroyJavaVM() {
return functions->DestroyJavaVM(this);
}
jint AttachCurrentThread(void **penv, void *args) {
return functions->AttachCurrentThread(this, penv, args);
}
jint DetachCurrentThread() {
return functions->DetachCurrentThread(this);
} jint GetEnv(void **penv, jint version) {
return functions->GetEnv(this, penv, version);
}
jint AttachCurrentThreadAsDaemon(void **penv, void *args) {
return functions->AttachCurrentThreadAsDaemon(this, penv, args);
}
#endif
};
可以看到,JNIInvokeInterface_和JavaVM_的定义非常类似于JNINativeInterface_与JNIEnv_,其用法也非常类似,这里不再过多介绍。
公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流
第40篇-JNIEnv和JavaVM的更多相关文章
- 第41篇-JNIEnv与JavaVM的初始化
JavaVM和JNIEnv的初始化和JVM各模块的初始化都是在JNI_CreateJavaVM()函数中完成.这一篇将详细介绍JavaVM和JNIEnv的初始化过程. 1.初始化JavaVM Java ...
- NDK(13)JNIEnv和JavaVM
转自: http://www.cnblogs.com/canphp/archive/2012/11/13/2768937.html JNIEnv是一个与线程相关的变量,不同线程的JNIEnv彼此独立 ...
- 【Android JNI】JNIEnv和JavaVM的区别
JNI的实现可涉及两个关键类:JNIEnv和JavaVM. JavaVM:这个代表java的虚拟机.所有的工作都是从获取虚拟机的接口开始的. 第一种方式,在加载动态链接库的时 ...
- Jni 线程JNIEnv,JavaVM,JNI_OnLoad(GetEnv返回NULL?FindClass返回NULL?)
此文章是关于NDK线程的第二篇理论知识笔记.主要有两个点,如下: 1.pthread_create(Too many arguements, expected 1) ?2.线程中如何获取JNIEnv? ...
- Java中JNI的使用详解第二篇:JNIEnv类型和jobject类型的解释
上一篇说的是一个简单的应用,说明JNI是怎么工作的,这一篇主要来说一下,那个本地方法sayHello的参数的说明,以及其中方法的使用 首先来看一下C++中的sayHello方法的实现: JNIEXPO ...
- Java中JNI的使用详解第三篇:JNIEnv类型中方法的使用
转自: http://blog.csdn.net/jiangwei0910410003/article/details/17466369 上一篇说道JNIEnv中的方法的用法,这一篇我们就来通过例子来 ...
- ICML 2018 | 从强化学习到生成模型:40篇值得一读的论文
https://blog.csdn.net/y80gDg1/article/details/81463731 感谢阅读腾讯AI Lab微信号第34篇文章.当地时间 7 月 10-15 日,第 35 届 ...
- 40 篇原创干货,带你进入 Spring Boot 殿堂!
两个月前,松哥总结过一次已经完成的 Spring Boot 教程,当时感受到了小伙伴们巨大的热情. 两个月过去了,松哥的 Spring Boot 教程又更新了不少,为了方便小伙伴们查找,这里再给大家做 ...
- 第40篇 使用Sublime+MarkDown快速写博客
原文地址:http://blog.laofu.online/2017/06/03/how-use-sublime/ 前端的开发人员应该都知道sublime的神器,今天就说说如何使用sublime结合m ...
随机推荐
- 关于selenium中的三种等待方式与EC模块的知识
1. 强制等待 第一种也是最简单粗暴的一种办法就是强制等待sleep(xx),强制让闪电侠等xx时间,不管凹凸曼能不能跟上速度,还是已经提前到了,都必须等xx时间. 看代码: 1 2 3 4 5 6 ...
- HTML 网页开发、CSS 基础语法——五. 编辑器
- python基础知识三——try与except处理异常语句
try/except介绍 与其他语言相同,在python中,try/except语句主要是用于处理程序正常执行过程中出现的一些异常情况,如语法错(python作为脚本语言没有编译的环节,在执行过程中对 ...
- docker-compose 搭建mongo集群
创建目录 在每台机器上操作此步骤 一.在编写容器文件之前的注意事项: 1.yaml文件的指令前端不能使用tab键只能使用空格 2.storage: 指令的对接只能使用 : 不能使用 = 冒号的后面要跟 ...
- 分布式应用开发 | SpringBoot+dubbo+zookeeper实现服务注册发现 | 远程服务调用
前言 通过新建两个独立服务--提供者.消费者,模拟两个独立分布的应用,通过使用dubbo+zookeeper来实现远程服务调用. 目录 项目搭建 provider-server consumer-se ...
- 微信小程序_快速入门02
01我们学习了环境的准备和简单的demo,现在是时候来学习简单的页面编写了,首先我们来学习一些常用的基础标签: 一.view盒子,就是类似于div的盒子,可以用来存其他元素的容器. 二.text 文本 ...
- I/O系统
I/O系统的组成 外部设备 接口部件 总线 相应的管理软件 I/O软件 将用户编制的程序(或数据)输入主机内 将运算结果输出给用户 实现输入输出系统与主机工作的协调 I/O系统的基本功能 完成计算机内 ...
- leetcode779 第k个语法符号。
直接找规律. 第一行 0 第二行 01 第三行 0110 第四行 01101001 可以发现,第n行的数量比第n-1行多了一倍,并且前半部分是和第n-1行一样的,后半部分是前半部分"按位取反 ...
- 使用docker部署nginx并配置https
我只有一台服务器,但我想在这台服务器上运行多个项目,怎么办? 总不能靠加端口区分吧? 百度和Google是个好东西,于是我找到了答案,使用nginx. 通过nginx,我可以给我的一台服务器配置两个域 ...
- [软工顶级理解组] Alpha阶段测试报告
[软工顶级理解组] Alpha阶段测试报告 在测试过程中发现了多少Bug? 测试阶段发现并已修复的bug: 尚且存在,但是难以解决或者不影响使用的bug: 计算重修课程的时候,如果重修课程的课程号和原 ...