Invocation API允许软件供应商将Java VM加载到任意本机应用程序中。 供应商可以提供支持Java的应用程序,而无需链接Java VM源代码。
本章首先概述了Invocation API。 接下来是所有Invocation API函数的参考页面。 它涵盖以下主题:
目录
概观
以下代码示例说明了如何在Invocation API中使用函数。 在此示例中,C ++代码创建Java VM并调用静态方法,称为Main.test
。 为清楚起见,我们省略了错误检查。
#include <jni.h> /* where everything is defined */
...
JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */
jvm->DestroyJavaVM();
此示例在API中使用三个函数。 Invocation API允许本机应用程序使用JNI接口指针来访问VM功能。 该设计类似于Netscape的JRI嵌入接口。
创建VM
JNI_CreateJavaVM()
函数加载并初始化Java VM并返回指向JNI接口指针的指针。 调用JNI_CreateJavaVM()
的线程被认为是主线程 。
附加到VM
JNI接口指针( JNIEnv
)仅在当前线程中有效。 如果另一个线程需要访问Java VM,它必须首先调用AttachCurrentThread()
以将其自身附加到VM并获取JNI接口指针。 一旦连接到VM,本机线程就像在本机方法中运行的普通Java线程一样工作。本机线程保持连接到VM,直到它调用DetachCurrentThread()
来分离自身。
附加的线程应该有足够的堆栈空间来执行合理的工作量。 每个线程的堆栈空间分配是特定于操作系统的。 例如,使用pthreads,可以在pthread_attr_t
参数中指定堆栈大小。
从VM分离
附加到VM的本机线程必须在退出之前调用DetachCurrentThread()
以分离自身。 如果调用堆栈上有Java方法,则线程无法分离。
卸载VM
JNI_DestroyJavaVM()
函数卸载Java VM。
VM等待直到 当前线程是实际卸载之前唯一的非守护程序用户线程。 用户线程包括Java线程和附加的本机线程。 存在此限制是因为Java线程或附加的本机线程可能正在保留系统资源,例如锁,窗口等。 VM
无法自动释放这些资源。 通过限制 当前线程是卸载VM时唯一正在运行的线程,释放任意线程持有的系统资源的负担在程序员身上。
库和版本管理
加载本机库后,所有类加载器都可以看到它。 因此,不同类加载器中的两个类可以使用相同的本机方法链接。 这导致两个问题:
- 类可能会错误地链接到由不同类加载器中具有相同名称的类加载的本机库。
- 本机方法可以轻松地混合来自不同类加载器的类。 这打破了类加载器提供的名称空间分离,并导致类型安全问题。
每个类加载器都管理自己的一组本机库。 无法将相同的JNI本机库加载到多个类加载器中。 这样做会导致抛出UnsatisfiedLinkError
。 例如, System.loadLibrary
在用于将本机库加载到两个类加载器时抛出UnsatisfiedLinkError
。 新方法的好处是:
- 基于类加载器的名称空间分隔保留在本机库中。 本机库无法轻松混合来自不同类加载器的类。
- 此外,当对应的类加载器被垃圾收集时,可以卸载本机库。
为了便于版本控制和资源管理,JNI库可选择导出以下两个函数:
JNI_OnLoad
jint JNI_OnLoad(JavaVM *vm, void *reserved);
VM在加载本机库时调用JNI_OnLoad
(例如,通过System.loadLibrary
)。 JNI_OnLoad
必须返回本机库所需的JNI版本。
为了使用任何新的JNI函数,本机库必须导出返回JNI_VERSION_1_2
的JNI_OnLoad
函数。 如果本机库未导出JNI_OnLoad
函数,则VM假定该库仅需要JNI版本JNI_VERSION_1_1
。 如果VM无法识别JNI_OnLoad
返回的版本号,则VM将卸载库并将其视为库从未加载。
JNI_Onload_L(JavaVM *vm, void *reserved);
如果库L是静态链接的,那么在第一次调用System.loadLibrary("L")
或等效的API时,将使用为JNI_OnLoad
函数指定的相同参数和预期返回值调用JNI_OnLoad
函数。 JNI_OnLoad_L
必须返回本机库所需的JNI版本。 此版本必须为JNI_VERSION_1_8
或更高版本。 如果VM无法识别JNI_OnLoad_L
返回的版本号,则VM将表现为从未加载库。
连锁:
从包含本机方法实现的本机库导出。
JNI_OnUnload
void JNI_OnUnload(JavaVM *vm, void *reserved);
当包含本机库的类加载器被垃圾收集时,VM调用JNI_OnUnload
。 此功能可用于执行清理操作。 因为在未知的上下文中调用此函数(例如来自终结器),程序员应该保守使用Java VM服务,并避免任意Java回调。
请注意, JNI_OnLoad
和JNI_OnUnload
是JNI库可选提供的两个函数,不是从VM导出的。
JNI_OnUnload_L(JavaVM *vm, void *reserved);
当包含静态链接的本机库L的类加载器被垃圾收集时,如果导出这样的函数,VM将调用库的JNI_OnUnload_L
函数。 此功能可用于执行清理操作。 因为在未知的上下文中调用此函数(例如来自终结器),程序员应该保守使用Java VM服务,并避免任意Java回调。
信息说明:
加载本机库的行为是使库及其本机入口点已知并注册到Java VM和运行时的完整过程。 请注意,仅仅执行操作系统级操作来加载本机库(例如UNIX(R)系统上的dlopen
)并不能完全实现此目标。 通常从Java类加载器调用本机函数来执行对主机操作系统的调用,该操作系统将库加载到内存中并将句柄返回到本机库。 此句柄将存储并用于后续搜索本机库入口点。 一旦成功返回句柄以注册库,Java本机类加载器将完成加载过程。
连锁:
从包含本机方法实现的本机库导出。
调用API函数
JavaVM
类型是指向Invocation API函数表的指针。 以下代码示例显示了此函数表。
typedef const struct JNIInvokeInterface *JavaVM;
const struct JNIInvokeInterface ... = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GetEnv,
AttachCurrentThreadAsDaemon
};
请注意,三个Invocation API函数JNI_GetDefaultJavaVMInitArgs()
, JNI_GetCreatedJavaVMs()
和JNI_CreateJavaVM()
不是JavaVM函数表的一部分。 可以在没有预先存在的JavaVM
结构的情况下使用这些函数。
JNI_GetDefaultJavaVMInitArgs
jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
返回Java VM的默认配置。 在调用此函数之前,本机代码必须将vm_args-> version字段设置为它期望VM支持的JNI版本。 此函数返回后,vm_args-> version将设置为VM支持的实际JNI版本。
连锁:
从实现Java虚拟机的本机库导出。
参数:
vm_args
:指向填充默认参数的JavaVMInitArgs
结构的指针。
返回值:
如果支持所请求的版本,则返回JNI_OK
; 如果不支持请求的版本,则返回JNI错误代码(负数)。
JNI_GetCreatedJavaVMs
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
返回已创建的所有Java VM。 指向VM的指针按创建顺序写入缓冲区vmBuf。 最多将写入bufLen条目数。 在* nVM中返回创建的VM的总数。
不支持在单个进程中创建多个VM。
连锁:
从实现Java虚拟机的本机库导出。
参数:
vmBuf
:指向将放置VM结构的缓冲区的指针。
bufLen
:缓冲区的长度。
nVMs
:指向整数的指针。
返回值:
成功时返回JNI_OK
; 失败时返回合适的JNI错误代码(负数)。
JNI_CreateJavaVM
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
加载并初始化Java VM。 当前线程成为主线程。 将env
参数设置为主线程的JNI接口指针。
不支持在单个进程中创建多个VM。
JNI_CreateJavaVM
的第二个参数始终是指向JNIEnv *
的指针,而第三个参数是指向JavaVMInitArgs
结构的指针,该结构使用选项字符串来编码任意VM启动选项:
typedef struct JavaVMInitArgs {
jint version;
jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;
options
字段是以下类型的数组:
typedef struct JavaVMOption {
char * optionString; / *该选项为默认平台编码中的字符串* /
void * extraInfo;
JavaVMOption;
数组的大小由JavaVMInitArgs
的nOptions字段JavaVMInitArgs
。 如果ignoreUnrecognized
是JNI_TRUE
,则JNI_CreateJavaVM
忽略以“ -X
”或“ _
”开头的所有无法识别的选项字符串。 如果ignoreUnrecognized
是JNI_FALSE
,则JNI_CreateJavaVM
会在遇到任何无法识别的选项字符串时立即返回JNI_ERR
。 所有Java VM必须识别以下一组标准选项:
optionString | 含义 |
---|---|
-D<name>=<value> |
设置系统属性 |
-verbose[:class|gc|jni] |
启用详细输出。 选项后面可以跟一个逗号分隔的名称列表,表明VM将打印哪种消息。 例如,“ - -verbose:gc,class ”指示VM打印GC和类加载相关消息。 标准名称包括: gc , class 和jni 。 所有非标准(VM特定)名称必须以“ X ”开头。 |
vfprintf |
extraInfo 是指向vfprintf 钩子的指针。 |
exit |
extraInfo 是指向exit 钩子的指针。 |
abort |
extraInfo 是一个指向abort 钩子的指针。 |
此外,每个VM实现可以支持其自己的一组非标准选项字符串。 非标准选项名称必须以“ -X
”或下划线(“ _
”)开头。 例如,JDK / JRE支持-Xms
和-Xmx
选项,以允许程序员指定初始和最大堆大小。 以“ -X
”开头的选项可从“ java
”命令行访问。
以下是在JDK / JRE中创建Java VM的示例代码:
JavaVMInitArgs vm_args;
JavaVMOption options[4];
options [0] .optionString =“ - Djava.compiler = NONE”; / *禁用JIT * /
options [1] .optionString =“ - Djava.class.path = c:\ myclasses”; / *用户类* /
options [2] .optionString =“ - Djava.library.path = c:\ mylibs”; / *设置本机库路径* /
options [3] .optionString =“ - verbose:jni”; / *打印与JNI相关的消息* /
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;
/ *请注意,在JDK / JRE中,不再需要调用
* JNI_GetDefaultJavaVMInitArgs。
* /
res = JNI_CreateJavaVM(&vm,(void **)&env,&vm_args);
if(res <0)......
连锁:
从实现Java虚拟机的本机库导出。
参数:
p_vm
:指向生成的VM结构所在位置的指针。
p_env
:指向将放置主线程的JNI接口指针的位置的指针。
vm_args
:Java VM初始化参数。
返回值:
成功时返回JNI_OK
; 失败时返回合适的JNI错误代码(负数)。
DestroyJavaVM
jint DestroyJavaVM(JavaVM * vm);
卸载Java VM并回收其资源。
任何线程,无论是否附加,都可以调用此函数。 如果附加了当前线程,则VM将等待,直到当前线程是唯一的非守护程序用户级Java线程。 如果未附加当前线程,则VM会附加当前线程,然后等待,直到当前线程是唯一的非守护程序用户级线程。
连锁:
JavaVM接口函数表中的索引3。
参数:
vm
:将被销毁的Java VM。
返回值:
成功时返回JNI_OK
; 失败时返回合适的JNI错误代码(负数)。
不支持卸载VM。
AttachCurrentThread
jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
将当前线程附加到Java VM。 返回JNIEnv
参数中的JNI接口指针。
尝试附加已附加的线程是无操作。
本机线程无法同时连接到两个Java VM。
当线程附加到VM时,上下文类加载器是引导加载程序。
连锁:
JavaVM接口函数表中的索引4。
参数:
vm
:当前线程将附加到的VM。
p_env
:指向当前线程的JNI接口指针所在位置的指针。
thr_args
:可以是NULL或指向JavaVMAttachArgs
结构的指针,以指定其他信息:
AttachCurrentThread
的第二个参数始终是指向JNIEnv
的指针。 AttachCurrentThread
的第三个参数是保留的,应该设置为NULL
。
您将指针传递给以下结构以指定其他信息:
typedef struct JavaVMAttachArgs {
jint version;
char * name; / *线程的名称作为修改的UTF-8字符串,或NULL * /
jobject group;; / * ThreadGroup对象的全局引用,或NULL * /
} JavaVMAttachArgs
返回值:
成功时返回JNI_OK
; 失败时返回合适的JNI错误代码(负数)。
AttachCurrentThreadAsDaemon
jint AttachCurrentThreadAsDaemon(JavaVM * vm,void ** penv,void * args);
与AttachCurrentThread相同的语义,但新创建的java.lang.Thread实例是一个守护进程 。
如果线程已通过AttachCurrentThread或AttachCurrentThreadAsDaemon连接 ,则此例程只是将penv指向的值设置为当前线程的JNIEnv 。 在这种情况下, AttachCurrentThread和此例程都不会对线程的守护程序状态产生任何影响。
连锁:
JavaVM接口函数表中的索引7。
参数:
vm :当前线程将附加到的虚拟机实例。
penv :指向当前线程的JNIEnv接口指针所在位置的指针。
args :指向JavaVMAttachArgs结构的指针。
退货
成功时返回JNI_OK
; 失败时返回合适的JNI错误代码(负数)。
例外
没有。
DetachCurrentThread
jint DetachCurrentThread(JavaVM *vm);
从Java VM分离当前线程。 该线程持有的所有Java监视器都将被释放。 等待此线程死亡的所有Java线程都会收到通知。
主线程可以从VM分离。
连锁:
JavaVM接口函数表中的索引5。
参数:
vm
:将从中分离当前线程的VM。
返回值:
成功时返回JNI_OK
; 失败时返回合适的JNI错误代码(负数)。
GETENV
jint GetEnv(JavaVM *vm, void **env, jint version);
连锁:
JavaVM接口函数表中的索引6。
参数:
vm
:将从中检索接口的虚拟机实例。 env
:指向当前线程的JNI接口指针所在位置的指针。 version
:请求的JNI版本。
返回值:
如果当前线程未附加到VM,则将*env
设置为NULL
,并返回JNI_EDETACHED
。 如果不支持指定的版本, JNI_EVERSION
*env
设置为NULL
,并返回JNI_EVERSION
。 否则,将*env
设置为适当的接口,并返回JNI_OK
。