目錄
第一章 介紹
第二章 設(shè)計(jì)機(jī)制
第三章 JNI類型和數(shù)據(jù)結(jié)構(gòu)
第四章 JNI函數(shù)(1)
第四章 JNI函數(shù)(2)
第四章 JNI函數(shù)(3)
第四章 JNI函數(shù)(4)
第五章 Invocation API
第五章 Invocation API
5.1 簡(jiǎn)介
Invocation API允許軟件提供商在原生程序中內(nèi)嵌Java虛擬機(jī)。因此可以不需要鏈接任何Java虛擬機(jī)代碼來提供Java-enabled的應(yīng)用程序。
以下代碼演示如何使用:
#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();
?
創(chuàng)建虛擬機(jī)
JNI_CreateJavaVM() 函數(shù)載入和初始化一個(gè)Java虛擬機(jī)。調(diào)用該函數(shù)的線程被視為是主線程(main thread)。
?
attach到虛擬機(jī)
JNI接口指針(JNIEnv)只在當(dāng)前線程有效,如果需要在另一個(gè)線程訪問Java虛擬機(jī),必須先調(diào)用 AttachCurrentThread() 來將自己 attach 到虛擬機(jī)來獲得JNI接口指針(JNIEnv)
被acttach到的線程必須有足夠的stack空間來執(zhí)行一定的工作。每個(gè)線程分配多少??臻g根據(jù)系統(tǒng)而不同。
?
detach虛擬機(jī)
一個(gè)attach到虛擬機(jī)的本地線程必須在退出前調(diào)用 DetachCurrentThread() 來和虛擬機(jī)detach。如果還有Java方法在call stack中,則這個(gè)線程不能detach。
?
unload虛擬機(jī)
使用 JNI_DestroyJavaVM() 函數(shù)來卸載(unload)一個(gè)Java虛擬機(jī)
虛擬機(jī)會(huì)等待(阻塞),直到當(dāng)前線程成為唯一的非守護(hù)進(jìn)程的用戶進(jìn)程(the only non-daemon user thread),才真正執(zhí)行 unload 操作。
用戶進(jìn)程(user thread)包括:
- Java線程(java threads)
- attached到虛擬機(jī)的本地線程(attached native threads)
為什么要做這樣的限制(強(qiáng)制等待),是因?yàn)镴ava線程和native線程可能會(huì)hold住系統(tǒng)資源,例如鎖,窗口等資源,而虛擬機(jī)不能自動(dòng)釋放這些資源。通過限制當(dāng)前線程是唯一的運(yùn)行中的用戶線程才unload虛擬機(jī),則將釋放這種系統(tǒng)資源的任務(wù)交給程序員自己來負(fù)責(zé)了。
?
5.2 庫和版本管理
在 JDK/JRE 1.1,一旦本地庫被加載,它對(duì)所有classLoader都可見。因此兩個(gè)被不同的classLoader加載的類可能鏈接到同一個(gè)本地方法。這會(huì)出現(xiàn)兩個(gè)問題:
- 一個(gè)類可能錯(cuò)誤的鏈接到了另一個(gè)classLoader載入的同名本地庫。
- 本地方法可以輕松的混合不同ClassLoader載入的類,這破壞了由ClassLoader提供的命名空間隔離,會(huì)可能引發(fā)類型安全問題。
在 JDK/JRE 1.2,每個(gè)ClassLoader都有自己的一組本地庫。一個(gè)本地庫一旦被一個(gè)ClassLoader加載后,則不允許再被其他ClassLoader重復(fù)加載了。否則會(huì)拋出 ``UnsatisfiedLinkError 異常。這樣的好處是:
- 由ClassLoader創(chuàng)建的命名空間隔離在本地庫也被保存下來了。一個(gè)本地庫不能輕易混合不同ClassLoader加載的類。
- 另外,本地庫可以在ClassLoader被垃圾收回時(shí)unload。
ClassLoader什么時(shí)候會(huì)被垃圾收回?
?
JNI_OnLoad
Java虛擬機(jī)在加載本地庫(native library)時(shí)(即調(diào)用 System.loadLibrary() )后,在加載本地庫到內(nèi)存之后,會(huì)尋找其內(nèi)部的 JNI_OnLoad 函數(shù),并執(zhí)行它。這個(gè)函數(shù)必須返回本地庫使用的 JNI版本號(hào)。
如果要使用新的JNI函數(shù),則必須返回高版本的JNI版本號(hào)。如果本地庫不提供 JNI_ONLoad 函數(shù),則虛擬機(jī)默認(rèn)它使用的是 JNI_VERSION_1_1版本。如果返回一個(gè)虛擬機(jī)不支持的JNI版本號(hào),則本地庫不能被加載。
?
JNI_OnUnload
虛擬機(jī)在本地庫被垃圾回收前,調(diào)用其 JNI_OnUnload 函數(shù)。這個(gè)函數(shù)用來執(zhí)行一個(gè)清理操作。因?yàn)楹瘮?shù)在不確定的情況下被調(diào)用(例如 finalizer),因此開發(fā)者應(yīng)該保守的使用Java虛擬機(jī)服務(wù),并避免任何Java回調(diào)函數(shù)。
注意:JNI_OnLoad 和 JNI_OnUnload 兩個(gè)函數(shù)是可選的,不是必須要有。
?
5.3 Invocation API函數(shù)
JavaVM類型是一個(gè)指向 Invocation API 函數(shù)表的指針。例如:
typedef const struct JNIInvokeInterface *JavaVM;
const struct JNIInvokeInterface ... = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GetEnv,
AttachCurrentThreadAsDaemon
};
?
JNI_GetDefaultJavaVMInitArgs
jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
返回Java虛擬機(jī)的默認(rèn)配置。
參數(shù):
-
vm_args:JavaVMIntArgs結(jié)構(gòu)體的指針,包含虛擬機(jī)默認(rèn)配置。
返回值:
成功返回 JNI_OK ,失敗返回負(fù)數(shù)。
?
JNI_GetCreatedJavaVMs
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
返回所有已經(jīng)創(chuàng)建過的Java虛擬機(jī)。
在 JDK/JRE 1.2, 不支持一個(gè)進(jìn)程創(chuàng)建多個(gè)Java虛擬機(jī)。
參數(shù):
-
vmBuf:虛擬機(jī)buffer,創(chuàng)建過的虛擬機(jī)會(huì)被放進(jìn)來。 -
bufLen:buffer的最大長(zhǎng)度。 -
nVMs: 虛擬機(jī)的總數(shù)。
返回值:
成功返回 JNI_OK ,失敗返回負(fù)數(shù)。
?
JNI_CreateJavaVM
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
加載和初始化一個(gè)Java虛擬機(jī),當(dāng)前線程作為主線程(main thread)。
在 JDK/JRE 1.2,不允許在同一個(gè)進(jìn)程創(chuàng)建多個(gè)Java虛擬機(jī)。
參數(shù):
p_vm:指向JavaVM的指針。p_env:指向JNIEnv指針的指針。-
vm_args: 虛擬機(jī)的參數(shù)。?
第3個(gè)參數(shù) vm_args 的結(jié)構(gòu)體為:
typedef struct JavaVMInitArgs {
jint version;
jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;
其中 version 必須大于等于 JNI_VERSION_1_2,
其中 nOptions 為 options 的數(shù)量.
其中 ignoreUnrecognized 設(shè)置為 JNI_TRUE ,則會(huì)忽視所有不被識(shí)別的以 -X 或 _ 開頭的參數(shù)字符串,如果設(shè)置為 JNI_FALSE ,則遇到不被識(shí)別的參數(shù)時(shí)JNI_CreateJavaVM 函數(shù)會(huì)返回 JNI_ERR
其中 options 的結(jié)構(gòu)體為:
typedef struct JavaVMOption {
char *optionString; /* the option as a string in the default platform encoding */
void *extraInfo;
} JavaVMOption;
另外,所有虛擬機(jī)的實(shí)現(xiàn)都支持它自己的非標(biāo)準(zhǔn)參數(shù)。非標(biāo)準(zhǔn)參數(shù)必須以 -X 或 _ 開頭。例如,JDK/JRE 支持 -Xms 和 -Xmx 參數(shù)來允許開發(fā)者指定初始化和最大的heap大小。
返回值:
成功返回 JNI_OK ,失敗返回負(fù)數(shù)。
使用實(shí)例:
JavaVMInitArgs vm_args;
JavaVMOption options[4];
options[0].optionString = "-Djava.compiler=NONE"; /* disable JIT */
options[1].optionString = "-Djava.class.path=c:\myclasses"; /* user classes */
options[2].optionString = "-Djava.library.path=c:\mylibs"; /* set native library path */
options[3].optionString = "-verbose:jni"; /* print JNI-related messages */
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;
/* Note that in the JDK/JRE, there is no longer any need to call
* JNI_GetDefaultJavaVMInitArgs.
*/
res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...
?
DestoryJavaVM
jint DestroyJavaVM(JavaVM *vm);
卸載一個(gè)Java虛擬機(jī),并收回它擁有的資源。
JDK/JRE 1.1 還沒有完全支持這個(gè)函數(shù)。在JDK/JRE 1.1 只有主線程才允許調(diào)用該函數(shù)。自從 JDK/JRE 1.2,任何線程,不管是否已經(jīng) attached,都可以調(diào)用該函數(shù),如果當(dāng)前線程已經(jīng) attached,則虛擬機(jī)會(huì)等待當(dāng)前線程作為唯一的非守護(hù)用戶線程。如果當(dāng)前線程沒有 attached,則先attached,再等待當(dāng)前線程作為唯一的非守護(hù)用戶線程。
JDK/JRE 1.1.2 不支持unload虛擬機(jī)。
參數(shù):
-
vm:需要被銷毀的虛擬機(jī)。
返回值:
成功返回 JNI_OK ,失敗返回負(fù)數(shù)。
?
AttachCurrentThread
jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
attach當(dāng)前線程到Java虛擬機(jī),返回JNI接口指針 JNIEnv 。
嘗試attach已經(jīng)attached過的線程不會(huì)執(zhí)行任何操作(no-op)。
一個(gè)本地線程不能同時(shí)attach到兩個(gè)不同的Java虛擬機(jī)。
當(dāng)前一個(gè)線程attach到虛擬機(jī),它的上下文ClassLoader是Bootstrap ClassLoader。
參數(shù):
-
vm:需要被attach到的虛擬機(jī)。 -
p_env:返回的當(dāng)前線程的JNI接口指針。 -
thr_args:JavaVMAttachArgs結(jié)構(gòu)體來指定附加信息,或傳入NULL
typedef struct JavaVMAttachArgs {
jint version; /* must be at least JNI_VERSION_1_2 */
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_OK ,失敗返回負(fù)數(shù)。
?
AttachCurrentThreadAsDaemon
jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);
和 AttachCurrentThread 類似,只是新創(chuàng)建的 java.lang.Thread 被設(shè)置為守護(hù)線程(daemon)。
JDK/JRE 1.4之后才有這個(gè)函數(shù)。
參數(shù):
-
vm:需要被attach到的虛擬機(jī)。 -
penv:返回的當(dāng)前線程的JNI接口指針。 -
args:JavaVMAttachArgs結(jié)構(gòu)體來指定附加信息,或傳入NULL
返回值:
成功返回 JNI_OK ,失敗返回負(fù)數(shù)。
?
DetachCurrentThread
從java虛擬機(jī)detach當(dāng)前線程。所有這個(gè)線程持有的Java監(jiān)視區(qū)(monitor)都會(huì)被釋放。
自從 JDK/JRE 1.2, 主線程可以從虛擬機(jī)detach。
參數(shù):
-
vm:需要detach的虛擬機(jī)。
返回值:
成功返回 JNI_OK ,失敗返回負(fù)數(shù)。
?
GetEnv
jint GetEnv(JavaVM *vm, void **env, jint version);
獲取當(dāng)前線程的JNI接口指針 JNIEnv
參數(shù):
-
vm:虛擬機(jī)實(shí)例。 -
env:放置返回的當(dāng)前線程的JNI接口指針。 -
version:JNI版本。
返回值:
如果當(dāng)前線程還沒有attach到虛擬機(jī),則設(shè)置 *env 為 NULL ,并返回 JNI_EDETACHED 。如果指定的JNI版本不被支持,則也設(shè)置 *env 為 NULL ,并且返回 JNI_EVERSION。否則設(shè)置 *env 為正常的接口,并返回 JNI_OK 。
?
結(jié)語
JNI作為Java虛擬機(jī)的接口,銜接了Java層和native層。并且涉及到ClassLoader和JVM的內(nèi)部實(shí)現(xiàn)等知識(shí),全面了解JNI可以作為一把鑰匙來打開JDK和JVM底層研究的大門。
參考資料:
- Java Native Interface官方技術(shù)手冊(cè)
- Google JNI官方文檔
- JNI教程與技術(shù)手冊(cè)
- http://www.kancloud.cn/owenoranba/jni/120449
- JNI官方規(guī)范中文版
- Java本地接口(WIKI)
- Dalvik虛擬機(jī)JNI方法的注冊(cè)過程分析
- JNI函數(shù)
- jni.h頭詳見詳解二
- OpenJDK源碼 /openjdk/hotspot/src/share/vm/prims/jni.h
- OpenJDK源碼 /openjdk/hotspot/src/share/vm/prims/jni.c
- Android JNI編程提高篇之二
- JNI引用于垃圾回收
- http://blog.csdn.net/autumn20080101/article/details/8646431
- C++訪問Java的String字符串對(duì)象