JNI技術(shù)規(guī)范 - 第五章 Invocation API

目錄

第一章 介紹
第二章 設(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_OnLoadJNI_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,

其中 nOptionsoptions 的數(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_argsJavaVMAttachArgs 結(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接口指針蝠嘉。
  • argsJavaVMAttachArgs 結(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è)置 *envNULL 嚎于,并返回 JNI_EDETACHED 掘而。如果指定的JNI版本不被支持,則也設(shè)置 *envNULL 于购,并且返回 JNI_EVERSION袍睡。否則設(shè)置 *env 為正常的接口,并返回 JNI_OK 价涝。

?

結(jié)語

JNI作為Java虛擬機(jī)的接口女蜈,銜接了Java層和native層。并且涉及到ClassLoader和JVM的內(nèi)部實(shí)現(xiàn)等知識(shí)色瘩,全面了解JNI可以作為一把鑰匙來打開JDK和JVM底層研究的大門伪窖。

參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市居兆,隨后出現(xiàn)的幾起案子覆山,更是在濱河造成了極大的恐慌,老刑警劉巖泥栖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件簇宽,死亡現(xiàn)場離奇詭異勋篓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)魏割,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門譬嚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钞它,你說我怎么就攤上這事拜银。” “怎么了遭垛?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵尼桶,是天一觀的道長。 經(jīng)常有香客問我锯仪,道長泵督,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任庶喜,我火速辦了婚禮小腊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘溃卡。我一直安慰自己溢豆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布瘸羡。 她就那樣靜靜地躺著,像睡著了一般搓茬。 火紅的嫁衣襯著肌膚如雪犹赖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天卷仑,我揣著相機(jī)與錄音峻村,去河邊找鬼。 笑死锡凝,一個(gè)胖子當(dāng)著我的面吹牛粘昨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窜锯,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼张肾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锚扎?” 一聲冷哼從身側(cè)響起吞瞪,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驾孔,沒想到半個(gè)月后芍秆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惯疙,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年妖啥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霉颠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荆虱,死狀恐怖蒿偎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情克伊,我是刑警寧澤酥郭,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站愿吹,受9級(jí)特大地震影響不从,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜犁跪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一椿息、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坷衍,春花似錦寝优、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至迁杨,卻和暖如春钻心,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铅协。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工捷沸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狐史。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓痒给,卻偏偏與公主長得像,于是被迫代替她去往敵國和親骏全。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苍柏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容