下面介绍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的更多相关文章

  1. 第41篇-JNIEnv与JavaVM的初始化

    JavaVM和JNIEnv的初始化和JVM各模块的初始化都是在JNI_CreateJavaVM()函数中完成.这一篇将详细介绍JavaVM和JNIEnv的初始化过程. 1.初始化JavaVM Java ...

  2. NDK(13)JNIEnv和JavaVM

    转自:  http://www.cnblogs.com/canphp/archive/2012/11/13/2768937.html JNIEnv是一个与线程相关的变量,不同线程的JNIEnv彼此独立 ...

  3. 【Android JNI】JNIEnv和JavaVM的区别

     JNI的实现可涉及两个关键类:JNIEnv和JavaVM. JavaVM:这个代表java的虚拟机.所有的工作都是从获取虚拟机的接口开始的.             第一种方式,在加载动态链接库的时 ...

  4. Jni 线程JNIEnv,JavaVM,JNI_OnLoad(GetEnv返回NULL?FindClass返回NULL?)

    此文章是关于NDK线程的第二篇理论知识笔记.主要有两个点,如下: 1.pthread_create(Too many arguements, expected 1) ?2.线程中如何获取JNIEnv? ...

  5. Java中JNI的使用详解第二篇:JNIEnv类型和jobject类型的解释

    上一篇说的是一个简单的应用,说明JNI是怎么工作的,这一篇主要来说一下,那个本地方法sayHello的参数的说明,以及其中方法的使用 首先来看一下C++中的sayHello方法的实现: JNIEXPO ...

  6. Java中JNI的使用详解第三篇:JNIEnv类型中方法的使用

    转自: http://blog.csdn.net/jiangwei0910410003/article/details/17466369 上一篇说道JNIEnv中的方法的用法,这一篇我们就来通过例子来 ...

  7. ICML 2018 | 从强化学习到生成模型:40篇值得一读的论文

    https://blog.csdn.net/y80gDg1/article/details/81463731 感谢阅读腾讯AI Lab微信号第34篇文章.当地时间 7 月 10-15 日,第 35 届 ...

  8. 40 篇原创干货,带你进入 Spring Boot 殿堂!

    两个月前,松哥总结过一次已经完成的 Spring Boot 教程,当时感受到了小伙伴们巨大的热情. 两个月过去了,松哥的 Spring Boot 教程又更新了不少,为了方便小伙伴们查找,这里再给大家做 ...

  9. 第40篇 使用Sublime+MarkDown快速写博客

    原文地址:http://blog.laofu.online/2017/06/03/how-use-sublime/ 前端的开发人员应该都知道sublime的神器,今天就说说如何使用sublime结合m ...

随机推荐

  1. Maven项目创建与配置(二)

    项目配置 1:添加Source Folder 右击项目>NEW>Source Folder maven规定必须创建一下几个Source Folder src/main/resources ...

  2. django安装DjangoUeditor富文本

    环境: pycharm,django1.11,python2.7 第一种:直接 pip install DjangoUeditor,直接从网上安装到pycharm 由于是直接安装,ueditor.ht ...

  3. 一文彻底掌握Apache Hudi异步Clustering部署

    1. 摘要 在之前的一篇博客中,我们介绍了Clustering(聚簇)的表服务来重新组织数据来提供更好的查询性能,而不用降低摄取速度,并且我们已经知道如何部署同步Clustering,本篇博客中,我们 ...

  4. bzoj3729-Gty的游戏【Splay,博弈论】

    正题 题目链接:https://darkbzoj.tk/problem/3729 题目大意 给出\(n\)个点的一棵树,第\(i\)个节点上有\(a_i\)个石子,然后每次可以选择不超过\(L\)个石 ...

  5. video 适配通屏展示、针对不同分辨率 禁止变形处理

    CSS object-fit 属性 object-fit: fill|contain|cover|scale-down|none|initial|inherit; 样式上 video{ height: ...

  6. java/ kotlin下的单例模式

    单例模式属于创建型模式, 顾名思义,就是说整个系统中只有一个该对象的实例. 为什么要使用单例模式? 1, 对于一些需要频繁创建,销毁的对象, 使用单例模式可以节省系统资源 2, 对于全局持有的对象,单 ...

  7. 树莓派3B搭建NODE-RED运行环境并构建数据流

    树莓派3B搭建NODE-RED运行环境并构建数据流 树莓派搭建Node-RED环境 树莓派自2015年开始是默认就带NODE-RED的,但是如今已是2018年:)自带的版本已经很老了,可通过下面的命令 ...

  8. 解析csv数据绘制曲线图

    一个解析csv数据的小工具,所做项目中要查看脉冲图谱,经理就让我这个刚入职的小萌新写了个小程序.同事将csv格式的脉冲数据发给我,我的想法就是,将这些csv里的数据作为纵轴,x++为横轴,绘制出折线图 ...

  9. Serverless 在大规模数据处理的实践

    作者 | 西流 阿里云技术专家 前言 当您第一次接触 Serverless 的时候,有一个不那么明显的新使用方式:与传统的基于服务器的方法相比,Serverless 服务平台可以使您的应用快速水平扩展 ...

  10. bzoj1067——SCOI2007降雨量(线段树,细节题)

    题目描述 我们常常会说这样的话:"X年是自Y年以来降雨量最多的".它的含义是X年的降雨量不超过Y年,且对于任意\(Y<Z<X\),Z年的降雨量严格小于X年.例如2002 ...