JNI学习之Invocation API

JNI学习之Invocation APIJNIInvocatio 资料的学习笔记

本文是对链接http://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创建,因而需要确保自身有足够的栈空间来执行必要的代码。

卸载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);

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);

GetEnv

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




















版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/208213.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午12:05
下一篇 2026年3月19日 下午12:05


相关推荐

  • spring boot 系列之五:spring boot 通过devtools进行热部署

    前面已经分享过四篇随笔:在上述代码操作的过程中肯定也发现了一个问题:哪怕是一个个小小的修改,都必须要重新启动服务才能使修改生效。那能不能通过配置实现springboot的热部署呢?答案是肯定的。

    2022年2月16日
    43
  • 深度解读《AI 智能体的上下文工程》:构建高效 Agent 的七个宝贵教训

    深度解读《AI 智能体的上下文工程》:构建高效 Agent 的七个宝贵教训

    2026年3月15日
    3
  • WIN32 API获取窗口句柄

    WIN32 API获取窗口句柄WIN32API获取窗口句柄2008-08-1305:28P.M. 关于如何获取窗口句柄,以及有哪些函数可供使用的简单讨论!首先罗列出一些获取句柄的win32api函数,然后简单说说他们的用途!最后说说是怎么理解和应用的。可用的win32api函数: 1.HWNDFindWindow(LPCTSTRlpClassName,L

    2022年7月21日
    15
  • JS实现前进后退刷新的各种方法

    JS实现前进后退刷新的各种方法这里介绍了JS前进、后台、刷新的各种方法。也解释了很多前进后退函数出问题的原因。下面是用按钮做前进后退的JS:<inputtype=buttonvalue=刷新onclick=”window.location.reload()”/><inputtype=buttonvalue=前进onclick=”window.history.go(1)”/><inputtype=buttonvalue=后退onclick=”window.history.go(-

    2022年7月25日
    9
  • Java通过JDBC连接SQLserver 2017

    Java通过JDBC连接SQLserver 2017最近老师让我们用 JDBC 使用 SQLserver 但是她给的 SQLserver 版本号太老了 而且在 win10 上会出现很多问题 所以我写下这篇文章记录一下 安装 SQLserver 我用的是 SQLserver201 企业版要收费 但是这个是免费的 在官网上可以下载 https www microsoft com zh cn sql server sql server edit

    2026年3月26日
    2
  • curl 命令详解

    curl 命令详解转载于:https://www.cnblogs.com/guixiaoming/p/8507268.htmlcurl是一种命令行工具,作用是发出网络请求,然后获取数据,显示在"标准输出

    2022年6月30日
    29

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号