阅读本文前需要了解什么是jvmti,jvmti全称称之为 JVM Tool Interface,有关jvmti更详细的知识,本文不再详细列出。大家可以借助百度来了解有关它更为详尽的内容。

在开源文件大行其道的今天,基于java种种解决方案和框架纷绘踏至而来,浩瀚如海看不完也学不尽。在采用这些解决方案和框架进行项目开发时,往往会出现当程序卡壳时,既无异常提示信息亦没有与之对应的日志输出的局面。每每出现这样的困境时,往往只能通过打断点来一步步调试跟踪来解决。更有甚者,基于某一底层的框架进行相应的开发时,受限于框架开发者的精力和时间等因素的影响,如果框架针对某异常处理设计的不合理,处理异常时没有向外抛出异常,同时又没有输出日志信息。当出现问题时,雪上加霜的是框架又没有提供源码用于打断点调试,此时只能借助通过反编译工具,阅读框架源码来尝试解决问题。每每出现这些困境,真希望有一种工具能够洞悉那些被框架“吃掉”没有向往抛出的异常,以便加快问题的解决步伐。

为了方便开发,一直都想做一个有关java异常查看的小工具。想了很长时间,想到了如下几种实现方式:

借助字节码工具,在每一个方法开头和结尾处插入java异常捕获代码。这种方式实现起来效率太低了,况且如果在方法体内,捕获异常并没有向外抛出的话,就算采用这种方式也看不到异常。

SpringMVC框架针对异常进行了统一的封装和处理,只要进行相应的扩展就能捕获到程序抛出的异常。这种实现方式较前一种比较看来,效率大大提高了,但是仍然没有解决前者提到的,如果应用程序内部自己“吃掉异常”,不向外抛出异常的话,依然无法捕捉到异常,而且这种实现实现方式仅仅局限于使用了SpringMVC框架的WEB应用程序,如果使用了其它的WEB架构或者非WEB的应用程序就会无能为力,局限性太强。

思来索去,想到java应用程序的运行肯定是离不开jvm的,不妨看一下jvm中有没有提供这样的扩展。在网上搜索了一番,发现jvm还真提供了这样的扩展。

示例代码,在main方法中吃掉异常之后,不作任何处理。

1 packagecom.github.torlight.jvmtit;2

3 /**

4 * Hello world!5 *6 */

7 public classApp {8

9 public static voidmain( String[] args ){10

11 System.out.println( "Hello World!");12

13 try{14 throw new NullPointerException("QQQ");15 } catch(Exception e) {16

17 }18 }19 }

程序加载相应的扩展,运行之后效果如下所示,可以看到在控制台上面,打印出空指针异常。如果不借助jvmti提供的异常事件进行相应的扩展话,控制台上就不会打印空指针异常信息。其实现原理也很简单,借助jvmti提供的异常事件进行相应的扩展,当jvm捕获到异常时,会回调针对该事件的扩展方法,在该方法体内部调用 printStackTrace 方法,打印异常提示信息。

1 java.lang.ClassNotFoundException: com.github.torlight.jvmtit.App2 at java.net.URLClassLoader.findClass(URLClassLoader.java:381)3 at java.lang.ClassLoader.loadClass(ClassLoader.java:424)4 at java.lang.ClassLoader.loadClass(ClassLoader.java:411)5 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)6 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)7 at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)8 java.lang.ClassNotFoundException: com.github.torlight.jvmtit.App9 at java.net.URLClassLoader.findClass(URLClassLoader.java:381)10 at java.lang.ClassLoader.loadClass(ClassLoader.java:424)11 at java.lang.ClassLoader.loadClass(ClassLoader.java:411)12 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)13 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)14 at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)15 java.lang.NullPointerException: QQQ

16 at com.github.torlight.jvmtit.App.main(App.java:14)17 Hello World!

18 loaded class name=run in Callback_JVMTI_EVENT_EXCEPTION method19 Exception: Ljava/lang/ClassNotFoundException;20 loaded class name=run in Callback_JVMTI_EVENT_EXCEPTION method21 Exception: Ljava/lang/ClassNotFoundException;22 loaded class name=run in Callback_JVMTI_EVENT_EXCEPTION method23 Exception: Ljava/lang/NullPointerException;24 agent onload

下面贴出针对jvmti Callback_JVMTI_EVENT_EXCEPTION 事件进行扩展的agent代码。

1 //这是主 DLL 文件。

2

3 #include "stdafx.h"

4

5 #include "jvmti_evt_ex.h"

6 #include

7 #include

8 #include

9 #include

10

11 void printStackTrace(JNIEnv* env, jobject exception) {

12 jclass throwable_class = (*env).FindClass("java/lang/Throwable");

13 jmethodID print_method = (*env).GetMethodID(throwable_class, "printStackTrace", "()V");

14 (*env).CallVoidMethod(exception, print_method);

15 }16

17 void JNICALL Callback_JVMTI_EVENT_EXCEPTION (jvmtiEnv *jvmti_env,18 JNIEnv*jni_env,19 jthread thread,20 jmethodID method,21 jlocation location,22 jobject exception,23 jmethodID catch_method,24 jlocation catch_location) {25

26 printf("loaded class name=%s\n", "run in Callback_JVMTI_EVENT_EXCEPTION method");27 char*class_name;28

29 jclass exception_class = jni_env->GetObjectClass(exception);30 jvmti_env->GetClassSignature(exception_class, &class_name, NULL);31 printf("Exception: %s\n", class_name);32

33 printStackTrace(jni_env, exception);34 }35

36

37 void JNICALL Callback_JVMTI_EVENT_Exception_Catch (jvmtiEnv *jvmti_env,38 JNIEnv*jni_env,39 jthread thread,40 jmethodID method,41 jlocation location,42 jobject exception) {43

44 char*class_name;45 jclass exception_class = jni_env->GetObjectClass(exception);46 jvmti_env->GetClassSignature(exception_class, &class_name, NULL);47 printf("Exception: %s\n", class_name);48

49 printStackTrace(jni_env, exception);50 }51

52

53 JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved){54 jvmtiEnv *jvmti =NULL;55

56 fprintf(stderr,"agent onload");57

58 //获取JVMTI environment

59 jint erno = vm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_1);60 if (erno !=JNI_OK) {61 fprintf(stderr, "ERROR: Couldn't get JVMTI environment");62 returnJNI_ERR;63 }64

65 //注册功能

66 jvmtiCapabilities capabilities;67 (void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));68 capabilities.can_generate_exception_events=1;69

70 jvmtiError error = jvmti->AddCapabilities(&capabilities);71 if(error !=JVMTI_ERROR_NONE) {72 fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");73 returnerror;74 }75

76 //设置JVM事件 (JVMTI_EVENT_EXCEPTION) 回调

77 jvmtiEventCallbacks ex_callbacks;78 ex_callbacks.Exception = &Callback_JVMTI_EVENT_EXCEPTION;79 error = jvmti->SetEventCallbacks(&ex_callbacks, (jint)sizeof(ex_callbacks));80 if(error !=JVMTI_ERROR_NONE) {81 fprintf(stderr, "ERROR: Unable to SetEventCallbacks JVMTI!");82 returnerror;83 }84

85 //设置事件通知

86 error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, (jthread)NULL);87 if(error !=JVMTI_ERROR_NONE) {88 fprintf(stderr, "ERROR: Unable to SetEventNotificationMode JVMTI!,the error code=%d",error);89 returnerror;90 }91

92 returnJNI_OK;93 }94

95 JNIEXPORT jint JNICALL96 Agent_OnAttach(JavaVM* vm, char *options, void *reserved){97 //do nothing

98

99 returnJNI_OK;100 }101

102 JNIEXPORT voidJNICALL103 Agent_OnUnload(JavaVM *vm){104 //do nothing

105

106 }

示例代码和agent代码均已经上传至github上面(链接地址:https://github.com/gittorlight/java-other/tree/master/jvmti_evt_ex),我是用 visual studio 2010 来编译agent的,编译的时候需要根据所下载的jdk是32位还是64位来选择相对应的头文件。我使用的是64位的jdk 1.8,所以使用的是64位的头文件。截图如下所示:

19796e907aa4f5e03e13a4f300daedd2.png

编译agent截图(一)

6d31ec7e386616b91d8e0933a376cd42.png

编译agent截图(二)

e5fe555b8fb866b4c27a551ad70d122c.png

编译agent截图(三)

编译完成agent之后,在应用程序的启动参数上面使用-agentpath 参数来加载该agent。以eclipse为例,截图如下所示:

0cfd16dd52798f4faddae8b1affe90d2.png

加载agent截图(一)

9bf106b54eb007c6ccf1dd4eb2befd28.png

加载agent截图(二)

5b7e07dabf2d0488358eccefa1a1cd82.png

加载agent截图(三)

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐