Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 382|回复: 0

java异常查看利器之使用 jvmti 的Callback_JVMTI_EVENT_EXCEPTION 事件查看异常

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-6-12 03:05:27 | 显示全部楼层 |阅读模式

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

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

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

    • 借助字节码工具,在每一个方法开头和结尾处插入java异常捕获代码。这种方式实现起来效率太低了,况且如果在方法体内,捕获异常并没有向外抛出的话,就算采用这种方式也看不到异常。
    • SpringMVC框架针对异常进行了统一的封装和处理,只要进行相应的扩展就能捕获到程序抛出的异常。这种实现方式较前一种比较看来,效率大大提高了,但是仍然没有解决前者提到的,如果应用程序内部自己“吃掉异常”,不向外抛出异常的话,依然无法捕捉到异常,而且这种实现实现方式仅仅局限于使用了SpringMVC框架的WEB应用程序,如果使用了其它的WEB架构或者非WEB的应用程序就会无能为力,局限性太强。

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

    JVM Tool Interface 链接地址:https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#Exception


     

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

     1 package com.github.torlight.jvmtit;
     2 
     3 /**
     4  * Hello world!
     5  *
     6  */
     7 public class App {
     8     
     9     public static void main( 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.App
     2     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.App
     9     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 method
    19  Exception: Ljava/lang/ClassNotFoundException;
    20 loaded class name=run in Callback_JVMTI_EVENT_EXCEPTION method
    21  Exception: Ljava/lang/ClassNotFoundException;
    22 loaded class name=run in Callback_JVMTI_EVENT_EXCEPTION method
    23  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 <stdio.h>
      7 #include <memory.h>
      8 #include <string.h>
      9 #include <jvmti.h>
     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         return JNI_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         return  error;
     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         return error;
     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         return  error;
     90     }
     91 
     92     return JNI_OK;
     93 }
     94 
     95 JNIEXPORT jint JNICALL
     96     Agent_OnAttach(JavaVM* vm, char *options, void *reserved){
     97         //do nothing
     98         
     99     return JNI_OK;
    100 }
    101 
    102 JNIEXPORT void JNICALL
    103     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位的头文件。截图如下所示:

      编译agent截图(一)

     

    编译agent截图(二)

     

    编译agent截图(三)

     

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

    加载agent截图(一)

     

     

    加载agent截图(二)

     

     

    加载agent截图(三)

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-2 07:49 , Processed in 0.067177 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表