下面介绍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. phpspider PHP 爬虫

    * 通过composer下载 composer require owner888/phpspider // composer.json { "require": { "o ...

  2. redis小结 1-1

    1.1什么是resis Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. 1.2Redis 与其他 key - value 缓存产品有以下三个特点 Redis支持 ...

  3. 解决samba和SELINUX 冲突

    在使用Samba进行建立Window与Linux共享时,要是不能访问,出现"您可能没有权限使用网络资源", 那就是SELinux在作怪了 要是想让共享目录能访问,可以使用命令 #s ...

  4. 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 百篇博客分析OpenHarmony源码 | v42.02

    百篇博客系列篇.本篇为: v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...

  5. 深入浅出WPF-11.Template(模板)03

    模板 如果把WPF窗体看做一个舞台的话,窗体上的控件就是演员,他们的职责就是在用户界面上按照业务逻辑的需呀哦扮演自己的角色.为了让同一个控件担当起不同的角色,程序员就要为他们设计多种外观样式和行为动作 ...

  6. 二、mybatis之数据输出

    上一篇我们做了一个入门案例,是我们做mybatis的基本步骤,不熟悉的可以回顾一下https://www.cnblogs.com/jasmine-e/p/15330355.html,在这篇文章中只是简 ...

  7. 为什么'\x1B'.length===1?\x与\u知识延伸

    背景 先讲一下背景,再说原因 大多数库都会在日志中使用chalk库为console的内容进行上色 被chalk处理后,其原本的内容会被'\x1B...'所包裹 console.log(chalk.bl ...

  8. bzoj1834 ZJOI2010网络扩容(费用流)

    给定一张有向图,每条边都有一个容量C和一个扩容费用W.这里扩容费用是指将容量扩大1所需的费用. 求: 1.在不扩容的情况下,1到N的最大流: 2.将1到N的最大流增加K所需的最小扩容费用. 其中\(n ...

  9. CAM 模板样式表

    视图 模板类型 模板子类型 类型 子类型 刀具类型 刀具子类型 加工工序 mill_planar FACE_MILLING_AREA 100 261     加工工序 mill_planar FACE ...

  10. 返回值优化 RVO

    <深度探索C++对象模型>-- 2.3 返回值的初始化 & 在编译器层面做优化