JNI学习之Invocation API

ttp://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html的学习笔记,限于英文水平和对JNI的理解,可能存在错误。

简介

通过使用Invocation API,使用C/C++开发的本地应用可以访问Java虚拟机提供的特性。为了描述简单,下面提到的VM指的都是Java虚拟机。

创建VM

在本地应用里,调用JNI_CreateJavaVM()方法可以完成初始化、加载VM,并返回指向新VM对象的一个指针。调用JNI_CreateJavaVM方法的线程,被称为主线程。

线程与VM的关联操作

JNIEnv对象并不是线程安全的,因此只能在当前线程使用。当需要跨线程使用JNIEnv对象时,需要通过调用AttachCurrentThread方法将当前线程与JVM进行关联,并得到一个指向JNIEnv对象的指针。当AttachCurrentThread方法调用成功之后,当前本地线程即可被VM感知。由于本地线程并不由JVM创建,因而需要确保自身有足够的栈空间来执行必要的代码。

调用者可以在本地线程中调用DetachCurrentThread来解除关联关系,以便于释放资源,否则会导致资源泄漏;但在本地线程的调用栈内仍有Java方法时,调用DetachCurrentThread方法可能会失败。

卸载VM

当VM使用完毕,就应当考虑停止VM并回收资源,通过调用JNI_DestroyJavaVM方法即可达到这一目的。在VM看来,用户线程包括VM在执行Java字节码时创建的Java线程,以及通过调用AttachCurrentThread方法进而与VM完成关联的本地线程。用户线程的代码在执行时,可能会持有比如锁、窗口之类的系统资源,为了简化VM的实现,VM把释放资源的操作留给程序员去做,VM要求调用JNI_DestroyJavaVM方法的当前线程必须是当前唯一存活的用户线程,否则JNI_DestroyJavaVM方法调用后可能无法达到预期的效果。

动态库和版本管理

本地动态库被VM加载之后,对于VM内部所有的类加载器都是可见的。即VM内部由不同类加载器加载的两个类可以关联到相同的本地方法,这带来两个问题:

  1. 一个Java类可能会与由另外一个类加载触发加载的本地库建立关联关系;
  2. 本地方法无法区分当前的调用是来自VM内部由不同类加载器加载的哪个类,这破坏了由类加载器控制的类命名空间,从而可能引入类型安全相关的问题;

为了解决上述的两个问题,引入了新的解决方法,即某个类加载器自己管理当前加载的本地库的集合,并且相同的本地库只能被一个类加载器加载。应用的代码违反这两点约束将导致UnsatisfiedLinkError的出现。

新方法的优点有:

  1. 基于类加载器实现的命名空间管理在本地库的使用方面得到了保留,本地库可以无需考虑来自不同类加载器的类的调用;
  2. 当加载某个本地库的类加载器被GC掉之后,本地库也可以自动被释放掉资源。

JNI_OnLoad

为了便于实现上述特性,VM暴露了方法JNI_OnLoad。在VM加载本地库时,VM会自动在本地库文件中查找这个方法,如果方法存在则通过这个方法来获取本地库使用的JNI版本号,这样VM可以决定本地库使用VM特性的请求是否合理。如果JNI_OnLoad方法没有实现,VM认为本地库基于JNI_VERSION_1_1相关的特性实现。如果VM无法识别JNI_OnLoad方法的返回值,VM会忽略本地库的加载请求,并清理现场。

JNI_OnUnLoad

当加载本地库的类加载器被GC之后,VM会主动调用本地库导出的JNI_OnUnLoad方法,如果本地库没定义这个方法的话,这个步骤将自动忽略。一般而言,可以在JNI_OnUnLoad方法内部做一些清理操作。由于JNI_OnUnLoad方法被VM回调的时机不确定,因而要避免在这个方法内部调用Java语言的方法以及VM提供的特性。

Invocation API简介

这一章节提到的API均由VM提供。方法的返回值为JNI_OK 时表示调用成功,非JNI_OK 表示调用失败。

如下是常用的几个结构定义。

typedef struct JavaVMInitArgs {	jint version;	jint nOptions;	JavaVMOption *options;	jboolean ignoreUnrecognized;} JavaVMInitArgs;typedef struct JavaVMOption {	char *optionString;/* the option as a string in the default platform encoding */	void *extraInfo;} JavaVMOption;typedef struct JavaVMAttachArgs {	jint version;	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;

JNI_GetDefaultJavaVMInitArgs

签名为jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
通过调用本方法,可以得到VM的默认配置属性。方法入参为指向JavaVMInitArgs类型对象的指针,在本地代码调用JNI_GetDefaultJavaVMInitArgs方法前需要设置期望VM支持的版本号。方法调用返回值为JNI_OK时表示成功,VM会将版本号设置为实际支持的值;方法的返回值非JNI_OK时,表示调用失败。

JNI_GetCreatedJavaVMs

签名为jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
通过调用本方法,可以获取到当前已创建的全部VM对象。vmBuf为保存指针的数组,长度由bufLen给出,而实际的VM数量将在nVMs变量中返回。
单个进程中不允许创建多个VM实例。

JNI_CreateJavaVM

签名为jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
创建VM对象,并完成必要的初始化操作,同时调用方法的线程被设置为主线程。在单个进程中不允许创建多个VM对象。

DestroyJavaVM

签名为jint DestroyJavaVM(JavaVM *vm);

通过调用本方法,可以卸载已创建的VM对象,并回收相关的资源。本方法是线程安全的,可以在任意线程使用。本方法在使用时会阻塞当前线程吗?假如调用线程没有与VM对象建立了关联关系,则直接建立关联关系,然后等待其它用户线程退出;假如已建立了关联关系,则直接等待其它用户线程退出。
Unloading of the VM is not supported.这语没有看明白什么意思。

AttachCurrentThread

签名jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
当前调用线程与VM建立关联关系,获取到可在当前线程安全使用的JNIEnv对象。假如当前线程已经与VM建立关联,多次调用本方法是安全的。本地线程在同一时段内,只能与一个VM对象建立关联关系(这个说法很奇怪,之前的资源提示在单个进程内,只允许创建一个VM,这里为什么又提示说避免与多个VM关联?)。
当本地线程与VM建立关联之后,线程使用的上下文类加载器将是VM的启动类加载器。

调用本方法时,第一个参数为指向VM对象的指针,第二个参数为JNIEnv类型对象的指针,第三个参数为JavaVMAttachArgs类型对象的指针,但没有实际用途,应当为设置为NULL(不过原文档对这个参数的介绍稍有点混乱,需要实际验证一下)。

AttachCurrentThreadAsDaemon

签名jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);

用法和参数与AttachCurrentThread方法类似,区别在于VM内部创建的java.lang.Thread对象将会是一个daemon。如果当前本地线程已经和VM建立关联,则多次调用AttachCurrentThread或者AttachCurrentThreadAsDaemon并不会修改java.lang.Thread对象的daemon属性。

DetachCurrentThread

签名jint DetachCurrentThread(JavaVM *vm);

当前线程与VM解除关联,本地线程持有的锁对象将全部释放。等待当前线程的Java线程将得到通知。主线程通过调用本方法,可以和VM解除关联。

GetEnv

签名为jint GetEnv(JavaVM *vm, void **env, jint version);

通过调用本方法,可以获取到当前线程的JNIEnv对象。如果当前线程与VM没有建立关联,则*env被设置为NULL,同时返回JNI_EDETACHED;如果传入的VM特性版本号不被支持,则*env被设置为NULL,同时返回JNI_EVERSION;如果调用成功,则*env被设置为正确的JNIEnv对象指针,同时返回JNI_OK。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中,可以使用JNI(Java Native Interface)调用C++代码。JNI是Java平台的一部分,它提供了一组API,使Java应用程序能够调用本地方法。开发人员可以使用JNI将Java应用程序与C++代码连接起来。 以下是通过JNI调用C++代码的基本步骤: 1. 编写C++代码:开发人员需要编写C++代码,实现所需的功能。在编写代码时,需要使用JNI提供的函数和宏定义。 2. 生成头文件:开发人员需要使用Java的`javah`命令生成C++头文件。该头文件包含了JNI函数和宏定义,可以被C++代码包含。 3. 实现本地方法:开发人员需要在Java类中定义本地方法,并在C++代码中实现这些方法。在C++代码中,使用JNI提供的函数和宏定义来访问Java对象和调用Java方法。 4. 编译和链接:开发人员需要将C++代码编译为动态链接库,并将Java代码与动态链接库链接。 以下是一个简单的示例,演示如何在Java中调用C++代码: 1. 编写C++代码: ```cpp #include <jni.h> JNIEXPORT jstring JNICALL Java_com_example_MyClass_myMethod(JNIEnv *env, jobject obj, jstring str) { const char *cstr = env->GetStringUTFChars(str, NULL); // 调用C++函数 std::string result = myFunction(cstr); env->ReleaseStringUTFChars(str, cstr); return env->NewStringUTF(result.c_str()); } ``` 2. 生成头文件: ```bash javah -jni com.example.MyClass ``` 3. 实现本地方法: ```java public class MyClass { public native String myMethod(String str); } ``` 4. 编译和链接: ```bash g++ -shared -o libmylib.so mycode.cpp -I/path/to/jdk/include -I/path/to/jdk/include/linux javac MyClass.java java -Djava.library.path=. MyClass ``` 这样,开发人员就可以使用JNI调用C++代码,实现Java和C++之间的交互。需要注意的是,JNI调用涉及到内存管理和类型转换等问题,开发人员需要仔细处理,以避免内存泄漏和类型错误等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值