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 簡(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_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的最大長(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,

其中 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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容