把一個JVM嵌入到本地程序中

本章講述如何把一個JVM嵌入到你的本地程序當中去。一個JVM可以看作就是一個本地庫克蚂。本地程序可以鏈接這個庫,然后通過“調用接口”(invocation interface)來加載JVM。實際上膛虫,JDK中標準的啟動器也就是一段簡單的鏈接了JVM的C代碼格粪。啟動器解析命令躏吊、加載JVM、并通過“調用接口”(invocation interface)運行JAVA程序帐萎。
7.1 創(chuàng)建JVM
我們用下面這段C代碼來加載一個JVM并調用Prog.main方法來演示如何使用調用接口比伏。
public class Prog {
public static void main(String[] args) {
System.out.println("Hello World " + args[0]);
}
}
下面是啟動器:

include <jni.h>

define PATH_SEPARATOR ';' /* define it to be ':' on Solaris */

define USER_CLASSPATH "." /* where Prog.class is */

main() {
JNIEnv *env;
JavaVM *jvm;
jint res;
jclass cls;
jmethodID mid;
jstring jstr;
jclass stringClass;
jobjectArray args;

ifdef JNI_VERSION_1_2

 JavaVMInitArgs vm_args;
 JavaVMOption options[1];
 options[0].optionString =
     "-Djava.class.path=" USER_CLASSPATH;
 vm_args.version = 0x00010002;
 vm_args.options = options;
 vm_args.nOptions = 1;
 vm_args.ignoreUnrecognized = JNI_TRUE;
 /* Create the Java VM */
 res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

else

 JDK1_1InitArgs vm_args;
 char classpath[1024];
 vm_args.version = 0x00010001;
 JNI_GetDefaultJavaVMInitArgs(&vm_args);
 /* Append USER_CLASSPATH to the default system class path */
 sprintf(classpath, "%s%c%s",
         vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
 vm_args.classpath = classpath;
 /* Create the Java VM */
 res = JNI_CreateJavaVM(&jvm, &env, &vm_args);

endif /* JNI_VERSION_1_2 */

 if (res < 0) {
     fprintf(stderr, "Can't create Java VM\n");
     exit(1);
 }
 cls = (*env)->FindClass(env, "Prog");
 if (cls == NULL) {
     goto destroy;
 }

 mid = (*env)->GetStaticMethodID(env, cls, "main",
                                 "([Ljava/lang/String;)V");
 if (mid == NULL) {
     goto destroy;
 }
 jstr = (*env)->NewStringUTF(env, " from C!");
 if (jstr == NULL) {
     goto destroy;
 }
 stringClass = (*env)->FindClass(env, "java/lang/String");
 args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
 if (args == NULL) {
     goto destroy;
 }
 (*env)->CallStaticVoidMethod(env, cls, mid, args);

destroy:
if ((env)->ExceptionOccurred(env)) {
(
env)->ExceptionDescribe(env);
}
(jvm)->DestroyJavaVM(jvm);
}
上面的代碼有條件地編譯一個初始化JDK1_1InitArgs這個structure。這個structure是JDK1.1下特有的疆导,盡管JDK1.2也會支持赁项,但JDK1.2引入了一個更通用的叫作JavaVMInitArgs的VM初始化structure。
常量JNI_VERSION_1_2在JDK1.2下定義澈段,JDK1.1下是沒有的悠菜。
當目標平臺是1.1時,C代碼首先調用JNI_GetDefaultJavaVMInitArgs來獲得默認的VM設置败富。這個調用會返回heap size悔醋、stack size、默認類路徑等信息兽叮,并把這些信息存放在參數vm_args中芬骄。然后我們把Prog.class所在的目錄附加到vm_args.classpath中。
當平臺目標是1.2時充择,C代碼創(chuàng)建了一個JavaVMInitArgs的structure德玫。VM的初始化參數被存放在一個JavaVMOption數組中。
設置完VM初始化structure后椎麦,C程序調用JNI_CreateJavaVM來加載和初始化JVM宰僧,傳入的前兩個參數:
1、 接口指針jvm,指向新創(chuàng)建的JVM琴儿。
2段化、 當前線程的JNIEnv接口指針env。本地代碼通過env指針訪問JNI函數造成。
當函數JNI_CreateJavaVM函數成功返回時显熏,當前本地線程已經把自己的控制權交給JVM。這時晒屎,它會就像一個本地方法一樣運行喘蟆。以后就可以通過JNI函數來啟動Prog.main方法。
接著鼓鲁,程序調用DestroyJavaVM函數來unloadJVM蕴轨。不幸的是,在JDK1.1和JDK1.2中你不能unloadJVM骇吭,它會一直返回一個錯誤碼橙弱。
運行上面的程序,產生如下輸出:
Hello World from C!
7.2 把本地程序和JVM鏈接在一起
通過調用接口燥狰,你可把invoke.c這樣的程序和一個JVM鏈接到一起棘脐。怎么樣鏈接JVM取決于本地程序是要和一個特定的VM一起工作,還是要和多個具體實現方式未知的不同VM一起工作龙致。
7.2.1 和一個己知的JVM鏈接到一起
這種情況下蛀缝,你可以把你的本地程序和實現了JVM的本地庫鏈接在一起。編譯鏈接成功后目代,你就可以運行得到的可執(zhí)行文件内斯。運行時,你可能會得到一個錯誤信息像啼,比如“無法找到共享庫或者動態(tài)鏈接庫”俘闯,在Windows下,錯誤信息可能會指出無法發(fā)現動態(tài)鏈接庫javai.dll(JDK1.1)或者jvm.dll(JDK1.2)忽冻,這時真朗,你需要把DLL文件加載到你的PATH環(huán)境變量中去。
7.2.2 和未知的多個JVM鏈接到一起
這種情況下僧诚,你就不能把本地程序直接和一個特定的庫鏈接在一起了遮婶。比如,JDK1.1的庫是javai.dll湖笨,而JDK1.2的庫是jvm.dll旗扑。
解決方案是根據本地程序的需要,用運行時動態(tài)鏈接來加載不同的VM庫慈省。例如臀防,下面的win32代碼,根據給定的VM庫的路徑找到JNI_CreateJavaVM函數的入口。
LoadLibrary和GetProcAddress是Win32平臺上用來動態(tài)鏈接的API袱衷。雖然LoadLibrary可以實現了JVM的本地庫的名字(如“jvm”)或者路徑(如“C:\jdk1.2\jre\bin\classic\jvm.dll”)捎废。最好把本地庫的絕對路徑傳遞給JNU_FindCreateJavaVM,讓LoadLibrary去搜索jvm.dll致燥,這樣程序就不怕環(huán)境變量被改變了登疗。
7.3 附加本地線程
假設,你有一個用C寫的服務器這樣的多線程程序嫌蚤。當HTTP請求進來的時候辐益,服務器創(chuàng)建許多本地線程來并行的處理HTTP請求。為了讓多個線程可以同時操作JVM脱吱,我們可能需要把一個JVM植入這個服務器荷腊。
圖7.1 把JVM嵌入WEB服務器
服務器上的本地方法的生命周期一般會比JVM要短,因此我們需要一個方法把本地線程附加到一個已經在運行的JVM上面急凰,然后在這個本地方法中進行JNI調用,最后在不打擾其它連接到JVM上的線程的情況下把這個本地線程和JVM分離猜年。
下面這個例子中抡锈,attach.c演示了怎么樣使用調用接口(invocation interface)把本地線程附加到VM上去,這段程序使用的是Win32線程API乔外。
/
Note: This program only works on Win32 */

include <windows.h>

include <jni.h>

JavaVM jvm; / The virtual machine instance */

define PATH_SEPARATOR ';'

define USER_CLASSPATH "." /* where Prog.class is */

void thread_fun(void *arg)
{
jint res;
jclass cls;
jmethodID mid;
jstring jstr;
jclass stringClass;
jobjectArray args;
JNIEnv env;
char buf[100];
int threadNum = (int)arg;
/
Pass NULL as the third argument */

ifdef JNI_VERSION_1_2

 res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);

else

 res = (*jvm)->AttachCurrentThread(jvm, &env, NULL);

endif

 if (res < 0) {
    fprintf(stderr, "Attach failed\n");
    return;
 }
 cls = (*env)->FindClass(env, "Prog");
 if (cls == NULL) {
     goto detach;
 }
 mid = (*env)->GetStaticMethodID(env, cls, "main", 
                                 "([Ljava/lang/String;)V");
 if (mid == NULL) {
     goto detach;
 }
 sprintf(buf, " from Thread %d", threadNum);
 jstr = (*env)->NewStringUTF(env, buf);
 if (jstr == NULL) {
     goto detach;
 }
 stringClass = (*env)->FindClass(env, "java/lang/String");
 args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
 if (args == NULL) {
     goto detach;
 }
 (*env)->CallStaticVoidMethod(env, cls, mid, args);

detach:
if ((env)->ExceptionOccurred(env)) {
(
env)->ExceptionDescribe(env);
}
(*jvm)->DetachCurrentThread(jvm);
}

main() {
JNIEnv *env;
int i;
jint res;

ifdef JNI_VERSION_1_2

 JavaVMInitArgs vm_args;
 JavaVMOption options[1];
 options[0].optionString =
     "-Djava.class.path=" USER_CLASSPATH;
 vm_args.version = 0x00010002;
 vm_args.options = options;
 vm_args.nOptions = 1;
 vm_args.ignoreUnrecognized = TRUE;
 /* Create the Java VM */
 res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

else

 JDK1_1InitArgs vm_args;
 char classpath[1024];
 vm_args.version = 0x00010001;
 JNI_GetDefaultJavaVMInitArgs(&vm_args);
 /* Append USER_CLASSPATH to the default system class path */
 sprintf(classpath, "%s%c%s",
         vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
 vm_args.classpath = classpath;
 /* Create the Java VM */
 res = JNI_CreateJavaVM(&jvm, &env, &vm_args);

endif /* JNI_VERSION_1_2 */

if (res < 0) {
fprintf(stderr, "Can't create Java VM\n");
exit(1);
}
for (i = 0; i < 5; i++)
/* We pass the thread number to every thread */
_beginthread(thread_fun, 0, (void )i);
Sleep(1000); /
wait for threads to start /
(
jvm)->DestroyJavaVM(jvm);
}
上面這段attach.c代碼是invoke.c的一個變形床三。與在主線程中調用Prog.main不同,本地代碼開啟了五個線程杨幼。開啟線程完成以后撇簿,它就會等待1秒鐘讓線程可以運行完畢,然后調用DestroyJavaVM來銷毀JVM差购。而每一個線程都會把自己附加到JVM上面四瘫,然后調用Prog.main方法,最后斷開與JVM的連接欲逃。
JNI_AttachCurrentThread的第三個參數需要傳入NULL找蜜。JDK1.2引入了JNI_ThreadAttachArgs這個structure。它允許你向你要附加的線程傳遞特定的信息稳析,如線程組等洗做。JNI_ThreadAttachArgs這個structure的詳細描述在13.2節(jié)里面,作為JNI_AttachCurrentThread的規(guī)范的一部分被提到彰居。
當程序運行函數DetachCurrentThread時诚纸,它釋放屬于當前線程的所有局部引用。
運行程序陈惰,輸出如下:
Hello World from thread 1
Hello World from thread 0
Hello World from thread 4
Hello World from thread 2
Hello World from thread 3
上面這些輸出根據不同的線程調試策略畦徘,可能會出現不同的順序。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市旧烧,隨后出現的幾起案子影钉,更是在濱河造成了極大的恐慌,老刑警劉巖掘剪,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堪簿,死亡現場離奇詭異,居然都是意外死亡府瞄,警方通過查閱死者的電腦和手機青抛,發(fā)現死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匾鸥,“玉大人蜡塌,你說我怎么就攤上這事∥鸶海” “怎么了馏艾?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奴愉。 經常有香客問我琅摩,道長,這世上最難降的妖魔是什么锭硼? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任房资,我火速辦了婚禮,結果婚禮上檀头,老公的妹妹穿的比我還像新娘轰异。我一直安慰自己,他們只是感情好暑始,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布搭独。 她就那樣靜靜地躺著,像睡著了一般廊镜。 火紅的嫁衣襯著肌膚如雪戳稽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天期升,我揣著相機與錄音惊奇,去河邊找鬼。 笑死播赁,一個胖子當著我的面吹牛颂郎,可吹牛的內容都是我干的。 我是一名探鬼主播容为,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乓序,長吁一口氣:“原來是場噩夢啊……” “哼寺酪!你這毒婦竟也來了?” 一聲冷哼從身側響起替劈,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤寄雀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后陨献,有當地人在樹林里發(fā)現了一具尸體盒犹,經...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年眨业,在試婚紗的時候發(fā)現自己被綠了急膀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡龄捡,死狀恐怖卓嫂,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情聘殖,我是刑警寧澤晨雳,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奸腺,受9級特大地震影響餐禁,放射性物質發(fā)生泄漏。R本人自食惡果不足惜洋机,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洋魂。 院中可真熱鬧绷旗,春花似錦、人聲如沸副砍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豁翎。三九已至角骤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間心剥,已是汗流浹背邦尊。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留优烧,地道東北人蝉揍。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像畦娄,于是被迫代替她去往敵國和親又沾。 傳聞我的和親對象是個殘疾皇子弊仪,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內容

  • 本章是JNI設計思想的一個概述,在講的過程中杖刷,如果有必要的話励饵,還會對底層實現技術的原理做說明。本章也可以看作是JN...
    738bc070cd74閱讀 456評論 0 0
  • 譯文地址:http://docs.oracle.com/javase/1.5.0/docs/guide/jni/s...
    一根煙的彈跳閱讀 2,080評論 0 0
  • _ 聲明: 對原文格式以及內容做了細微的修改和美化, 主要為了方便閱讀和理解 _ 一. 基礎 Java Nativ...
    元亨利貞o閱讀 5,921評論 0 34
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理滑燃,服務發(fā)現役听,斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 抽茅針不瓶,害毛病禾嫉,請醫(yī)生,扎一針蚊丐! 看見美味的茅針熙参,不由地喚起了兒時的記憶,在心里念起了小時候看見別的小朋友抽茅針時...
    藻華閱讀 3,382評論 1 7