agile

来源: BlogBus 原始链接: http://agilejava.blogbus.com:80/logs/2004/12/535172.html 存档链接: https://web.archive.org/web/20061105125710id_/http://agilejava.blogbus.com:80/logs/2004/12/535172.html


<< 最近不顺 | 首 页 | 升级RED HAT 8 内核到2.6.9 >> 第一次尝试jvmti JVMTI是JDK1.5众多最新特性的一项,通过JVMTI我们可以做到许多以前难以做 到的事情.很多时候,最近我对程序建立了多少个对象以及每个对象的大小很感兴 趣.看了一些资料,知道可以通过代码注入来实现,也就是向java.lang.Object的构造 方法中注入代码,类似下面的代码: public Object(){ if(HeapTracker.engaged>0){ HeapTracker.newobj(this); } } 而Trace的代码类似下面的代码: public class HeapTracker { private static int engaged = 0; private static native void _newobj(Object thread, Object o); public static void newobj(Object o) { System.out.println("create new object"); if ( engaged != 0 ) { _newobj(Thread.currentThread(), o); } } private static native void _newarr(Object thread, Object a); public static void newarr(Object a) { if ( engaged != 0 ) { _newarr(Thread.currentThread(), a); } } } 关键在于如何向Object中注入那段代码,以及Trace的newobj方法的实现.在JDK1.5以前,在JVM已经启动的情况下是,把代码注入到java.lang.Object里就我目前所知是办不到的.另一种方法就是将jre的java.lang.Object用事先以经注入代码的一个"私有"的java.lang.Object替换调.初一看这种方法也不错,但这只是眼前的要求满足了.比如现在要求只检查java.lang.String耗费了多少内存,那我们又要替换它了.因此,一个比较好的方法是在类加载的时候来更改Class code,第一个想到的是定义自己的ClassLoader,问题也来了.在SUN的JDK里,bootclassloader会先加载jdk的核心类,根本就没有给我们的ClassLoader机会.在JDK1.5中,机会来了,就是通过JVMTI. 有关JVMTI的介绍,有很多的资料,大体的意思就是JVM提供了一些接口,你可以向 JVM注册你感兴趣的事件,比如我们这里感兴趣的是JVM加载类的时候,我们希望有 途径把我们自己的代码有选择的注入到类里.在JVMTI中,这个事件就是JVMTI_EVENT_CLASS_FILE_LOAD_HOOK .当JVM加载类的时候,会调用我们注册的回 调函数,而我们就可以在这个函数里做自己想做的事。 要实现这个目的,就要实现一个动态库,也就是JVMTI中的agent,在这个动态库中, 有两个函数是必须的:Agent_OnLoad和Agent_OnUnload,他们是JVM与代理交互的接口.当一个代理被JVM加载后,首先调用Agent_OnLoad这个方法;同样的,代理被JVM卸载后马上会调用Agent_OnUnload.我们就在Agent_OnLoad这个方法里注册感兴趣的事件,之后JVM就会在"有事"的时候通知我们.要使用agent,就要在启动JVM的时候告诉他要用到那个agent: java -agentlib:< agentLibName

这个 agentLibName 就是有我实现的动态库的名字,因系统而定,在Windows是 agentLibName .dll,而Unix/Linux是 agentLibName .so. 下面的代码简单的描述了这个结构: JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { //回调的接口 jvmtiEventCallbacks callbacks; .... //告诉JVM我们对所有的类加载都感兴趣 capabilities.can_generate_all_class_hook_events = 1; .... //注册处理类加载的函数,每当有类加载的时候,cbClassFileLoadHook所指 // 向的函数就会被调用 callbacks.ClassFileLoadHook = &cbClassFileLoadHook; ... //如果没有错误,返回JNI_OK,JVM会认为agent加载成功,否则会认为agent无 效 return JNI_OK; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM vm) { //可以在这里做一些清除的工作 } 到这里基本的方向已经清楚了,可以分步实现了.我做的例子是参照jdk1.5的demo 做的,可以在这里找到%JAVA_HOME%demojvmtiheapTracker %JAVA_HOME%demojvmtijava_crw_demo,基本上是对这两个例子的修修补补,先 说一下环境: OS:WINDOWS 2000 SERVER JDK:JDK1.5 C COMPILER:VC++ 6.0 由于C编程的经验少的可怜,在实现的过程遇到不少的问题,整个例子断断续续用 了三周的时间才大体上可用了:( 下面把我实现的过程大体讲一下,和大家分享,有错误的地方请指正或发邮件给我 agile.javaATgmail.com ^_^ 1.首先由javah生成HeapTracker的头文件: / DO NOT EDIT THIS FILE - it is machine generated / #include <jni.h> / Header for class HeapTracker / #ifndef _Included_HeapTracker #define _Included_HeapTracker #ifdef __cplusplus extern "C" { #endif /

  • Class: HeapTracker
  • Method: _newobj
  • Signature: (Ljava/lang/Object;Ljava/lang/Object;)V */ JNIEXPORT void JNICALL Java_HeapTracker__1newobj (JNIEnv , jclass, jobject, jobject); /
  • Class: HeapTracker
  • Method: _newarr
  • Signature: (Ljava/lang/Object;Ljava/lang/Object;)V */ JNIEXPORT void JNICALL Java_HeapTracker__1newarr (JNIEnv , jclass, jobject, jobject); #ifdef __cplusplus } #endif #endif 2.实现这两个方法,这里我把他们放到我的agent的实现中,一个dll两种用途:) / Java Native Method for Object. / JNIEXPORT void JNICALL Java_HeapTracker__1newobj(JNIEnv env, jclass klass, jthread thread, jobject o) { jlong object_size = 0; jlong * size = &object_size; printf("CREATE object,it's size is size %dn",object_size); } / Java Native Method for newarray / JNIEXPORT void JNICALL Java_HeapTracker__1newarr(JNIEnv env, jclass klass, jthread thread, jobject a) { //未实现 } 3.实现agent,这个agent的实现基本上就是heapTracker的拷贝,但在实现的过程 出了意想不到的情况。原来的heapTracker的实现,在cbClassFileLoadHook 这个方法里,他调用了java_crw_demo这个dll中的方法,首先无法连接,没有 lib;如果使用动态加载dll,那么在运行时会出错,说找不到java_crw_demo这个 方法.为了能连接通过,把java_crw_demo重新编译,而java_crw_demo也没法编 译,几个头文件找不到,jvm.h,opcodes.h和opcodes.length,其实这几个文件可 以在jdk.1.5的source中找到,把java_crw_demo.c用到的一些定义拷贝到 java_crw_demo.h就可以了.忘了说了,java_crw_demo在这里的工作就是完成代 码的注入的.这样应该可以编译通过了.试着运行一下: java -Xbootclasspath/a:heapTracker.jar -agentlib:lm Test heapTraker.jar中是HeapTracker.class,-agentlib:lm中的 heapTrakcer是我生成的dll名--lm.dll. 结果呢?错误!为什么?因为VC6生成的 dll有个问题,就是在a.dll中分配的内存 如果在b.dll中释放,会有运行时的异常,大体的意思是无效的指针.这是我在水 木清华上无意中看到的一个文章.而这里的问题是heapTracker中用了由 java_crw_demo修改过的Class Code的一个内存指针,其意思是在heapTracker 用完之后由heapTracker释放这块内存,但vc6就是不行,不知道大家有没有遇到 这样的情况,还是我做的不对?当时都已经想要放弃了:(. 后来通过一个回调函数解决了,又得改java_crw_demo中的方法了:(. 先改头文件java_crw_demo.h: //定义一个指针函数,用来回调用的 typedef void ( crwhandler)(unsigned char * image,int len); typedef void (JNICALL JavaCrwDemo)( . . . //在最后加上回调 crwhandler handler ); JNIEXPORT void JNICALL java_crw_demo( . . . //同样在最后加上回调 crwhandler handler ); 定义一个crwhandler的实现方法: static void handler(unsigned char * new_file_image,int newLength) { if ( newLength > 0 ) { enterCriticalSection(gdata->jvmti); { unsigned char jvmti_space; jvmti_space = (unsigned char )allocate(gdata->jvmti, (jint)newLength); (void)memcpy((void)jvmti_space, (void)(new_file_image), (int)newLength); gdata->new_class_data_len = (jint)newLength; gdata->new_class_data = jvmti_space; } exitCriticalSection(gdata->jvmti); } } 原来的hepaTracker.c中的cbClassFileLoadHook也要修改,在调用java_crw_demo 时,要在最后加上&handler,同时把它释放内存的代码去掉. 接下来修改java_crw_demo.c中的java_crw_demo方法: . . . / Dispose or shrink the space to be returned. / if ( new_length == 0 ) { (void)free(new_image); new_image = NULL; } else { new_image = (void)realloc((void)new_image, (int)new_length); / Return the new class image */ *pnew_file_image = (unsigned char *)new_image; *pnew_file_len = (long)new_length; //own_handler是由调用者传如的回调函数,这里是由heapTracker.c中 的cbClassFileLoadHook传入的 if(own_handler != NULL) { own_handler(*pnew_file_image,pnew_file_len); } //如果在代码外部free new_image,那么在vc6的环境一下,是不可操作的 free(new_image); / Cleanup before we leave. / } . . . 到现在再运行,应该没有指针的异常 了.可是有新的异常了 java.lang.StackOverflow:( 找了好久的原因,才发现向java.lang.Object中注入的代码有问题,现在注入的代 码类似于: public Object(){ HeapTracker.newobj(this); } 这样子的话,在JVM启动的时候,要建立一些对象,会导致一个递归的调用,最后JVM 崩溃:( 因此,这里还需要对java_crw_demo做一些改动,使它在注入代码时要注入类似下 下面的代码: if(HeapTracker.engaged>0){ HeapTracker.newobj(this); } 这涉及到class byte code 的知识,修改后的java_crw_demo.c中的 injection_template方法类似下面的代码: . . . if ( push_mnum ) { nbytes += push_short_constant_bytecodes(bytecodes+nbytes, mi->number); } / 下面的代码是用来实现: if(HeapTracker.engaged>0){ HeapTracker.newobj(this); } */ bytecodes[nbytes++] = (ByteCode)opc_getstatic; bytecodes[nbytes++] = (ByteCode)(ci->trace_field_index >> 8); bytecodes[nbytes++] = (ByteCode)ci->trace_field_index; bytecodes[nbytes++] = (ByteCode)opc_ifne; bytecodes[nbytes++] = (ByteCode)0; bytecodes[nbytes++] = (ByteCode)3; bytecodes[nbytes++] = (ByteCode)opc_aload_0; . . . 至此,基本就可以工作了:) 最近工作忙,没时间研究jvmit了,只好先放一下,欢迎大家和我一起研究、学习。^_) 昨天晚上写完这些已经2点多了,还好今天没有迟到,不过忘记带门卡了:( Tag: java技术 agilejava @ | 引用(0) | 编辑 评论 http://forum.javaeye.com/viewtopic.php?t=17310&highlight=&sid=1eeb18747c4b94dd6c9c1c7f1ec81c65 agilejava可否帮我看一下上面地址的一个问题,因为我想用jvmti来做,只想知道jvmti能否达到我所要的要求。我的msn:woyaoying@hotmail.com,很希望能跟您交流交流。 agilejava 回复 woyaoying 说: jvmti是在jvm里运行的,通过jvmti可以得知jvm是否已经启动了,而是否停止有时间是没有机会得到的,比如在linux下通过kill -9 来杀一个进程,是无法捕获这个事件的,我认为jvmti只适应你的要求的一小部分,如果需要监视进程,最好是外部监视。 (2005-12-05 16:26) woyaoying ( ) 发表于 2005-12-05 09:52 hehe,写的很不错阿,不过没看懂......不太熟悉这些方面的东西. agilejava 回复 is 说: 我基本就是把自己做过程写了一遍,也不知道有没有问题了:( (2005-02-23 09:07) is ( was.io8.org/me/ ) 发表于 2005-02-23 00:04 写的不错 agilejava 回复 mfc42d 说: mfc42d,你www.cn-java.com是你的吗?很好啊:) (2004-12-10 16:20) mfc42d ( www.cn-java.com ) 发表于 2004-12-10 13:22 发表评论 姓名: E-mail: 地址: