把一个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
上面这些输出根据不同的线程调试策略,可能会出现不同的顺序。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • 本章是JNI设计思想的一个概述,在讲的过程中,如果有必要的话,还会对底层实现技术的原理做说明。本章也可以看作是JN...
    738bc070cd74阅读 452评论 0 0
  • 译文地址:http://docs.oracle.com/javase/1.5.0/docs/guide/jni/s...
    一根烟的弹跳阅读 2,064评论 0 0
  • _ 声明: 对原文格式以及内容做了细微的修改和美化, 主要为了方便阅读和理解 _ 一. 基础 Java Nativ...
    元亨利贞o阅读 5,903评论 0 34
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 抽茅针,害毛病,请医生,扎一针! 看见美味的茅针,不由地唤起了儿时的记忆,在心里念起了小时候看见别的小朋友抽茅针时...
    藻华阅读 3,369评论 1 7