目錄
第一章 介紹
第二章 設(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 簡介
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è)線程分配多少椢笾ぃ空間根據(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的最大長度惜傲。 -
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ì)象