把一個JVM嵌入到本地程式中
本章講述如何把一個JVM嵌入到你的本地程式當中去。一個JVM可以看作就是一個本地庫。本地程式可以連結這個庫,然後通過“呼叫介面”(invocation interface)來載入JVM。實際上,JDK中標準的啟動器也就是一段簡單的連結了JVM的C程式碼。啟動器解析命令、載入JVM、並通過“呼叫介面”(invocation interface)執行JAVA程式。
7.1 建立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程式碼建立了一個JavaVMInitArgs的structure。VM的初始化引數被存放在一個JavaVMOption陣列中。
設定完VM初始化structure後,C程式呼叫JNI_CreateJavaVM來載入和初始化JVM,傳入的前兩個引數:
1、 介面指標jvm,指向新建立的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的本地庫連結在一起。編譯連結成功後,你就可以執行得到的可執行檔案。執行時,你可能會得到一個錯誤資訊,比如“無法找到共享庫或者動態連結庫”,在Windows下,錯誤資訊可能會指出無法發現動態連結庫javai.dll(JDK1.1)或者jvm.dll(JDK1.2),這時,你需要把DLL檔案載入到你的PATH環境變數中去。
7.2.2 和未知的多個JVM連結到一起
這種情況下,你就不能把本地程式直接和一個特定的庫連結在一起了。比如,JDK1.1的庫是javai.dll,而JDK1.2的庫是jvm.dll。
解決方案是根據本地程式的需要,用執行時動態連結來載入不同的VM庫。例如,下面的win32程式碼,根據給定的VM庫的路徑找到JNI_CreateJavaVM函式的入口。
LoadLibrary和GetProcAddress是Win32平臺上用來動態連結的API。雖然LoadLibrary可以實現了JVM的本地庫的名字(如“jvm”)或者路徑(如“C:\jdk1.2\jre\bin\classic\jvm.dll”)。最好把本地庫的絕對路徑傳遞給JNU_FindCreateJavaVM,讓LoadLibrary去搜尋jvm.dll,這樣程式就不怕環境變數被改變了。
7.3 附加本地執行緒
假設,你有一個用C寫的伺服器這樣的多執行緒程式。當HTTP請求進來的時候,伺服器建立許多本地執行緒來並行的處理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節裡面,作為JNI_AttachCurrentThread的規範的一部分被提到。
當程式執行函式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
上面這些輸出根據不同的執行緒除錯策略,可能會出現不同的順序。
相關文章
- 如何把Qlik Sense嵌入到Web應用中Web
- 如何在ppt中加入一個excel檔案 如何把excel嵌入到pptExcel
- Oracle中把一個查詢結果插入到一張表中Oracle
- data型別的Url格式:把小資料直接嵌入到Url中型別
- aix掛載光碟機並把內容複製到本地硬碟中AI硬碟
- matplotlib嵌入到pyqt中QT
- 用exp/imp把遠端資料匯入到本地資料庫中資料庫
- 把Iptables移植到嵌入式Linux系統(轉)Linux
- embed-c:可將C程式碼直接嵌入到Rust程式碼中C程式Rust
- 把本地專案上傳到github 不使用eclipseGithubEclipse
- 如何把本地網站部署到雲伺服器上網站伺服器
- 如何用JMX連線本地JVM的Java程式JVMJava
- 把EXCEL表格插入到PPT中Excel
- goland 把多個專案視窗合併到一個視窗GoLand
- 把一個一中的欄位更新另一個表中的t-sqlSQL
- 把一個python程式改寫成JuliaPython
- 把一個Python程式改寫為JuliaPython
- git clone一個laravel的專案到本地並執行GitLaravel
- 如何把jboss加入到jbuilder中UI
- 如何用nginx在本地把9000埠轉發到80埠上Nginx
- 如何把本地的Django專案部署到伺服器(親測)Django伺服器
- Mobile Web 除錯指南(1):把靜態資源指向到本地Web除錯
- 又一個基於JVM的程式語言:FlixJVM
- 物件陣列 根據key 把一樣的push到一個陣列物件陣列
- 把一個程式註冊成系統服務
- 從遠端把mysql透過mysqldump備份資料庫到本地MySql資料庫
- mysql 從一個表中查詢,插入到另一個表中MySql
- 插入一個檔案到DB中
- Oracle如何把一個表匯出匯入到另一個伺服器上的另一個表裡Oracle伺服器
- 如何 把下面這個url 重寫 用response 重寫到請求中...
- 如何用JMX連線本地JVM上執行的Java程式JVMJava
- 一個把方陣做對稱變換的程式
- 一個巧合,我把文件寫進了程式碼裡
- NGINX + SSH Tunnel 把本地開發環境公佈到網際網路上Nginx開發環境
- 如何將背景音樂嵌入到PPT檔案中
- 如何實現一個高效的本地日誌收集程式
- 法官正式命令微軟把Java程式嵌入視窗作業系統 (轉)微軟Java作業系統
- java高階用法之:在JNA中將本地方法對映到JAVA程式碼中Java