美文网首页jvm
利用jvmti查看java异常

利用jvmti查看java异常

作者: xpbob | 来源:发表于2020-03-18 01:52 被阅读0次

    日常监控中,异常信息,可以说是一个常见的指标,但是有些时候,因为使用不当(线程池submit提交,但是没有取异常,也没有改造线程池),或者是逻辑原因,出现了吞异常的情况,这个对我们异常的分析带来了一定的影响。所以异常的监控不能单独从日志中检索。

    异常throw事件

    jvmti中提供了两个异常的事件,一个是包含throw和catch,一个是catch。选择功能多的那个方便一点。

    void JNICALL
    Exception(jvmtiEnv *jvmti_env,
                JNIEnv* jni_env,
                jthread thread,
                jmethodID method,
                jlocation location,
                jobject exception,
                jmethodID catch_method,
                jlocation catch_location)
    

    我们通过函数可以发现我们可以获取到异常的线程,出异常的方法,行号,异常对象,catch的方法和行号。
    通过函数的参数,我们基本已经可以了解大概能获取的数据了。这里由于是触发的throw事件,所以如果只是new Exception的操作是不会触发事件的。有些代码通过创建Exception或者Error来控制逻辑,只要不是throw,catch的这种逻辑,这里是检测不到的。
    如果异常只throw没有catch的话,catch的字段就是空的。

    简易的实现

    我们就打印异常堆栈,然后输出触发的方法和代码行数,捕获的方法和代码行数。
    jvmti的日常操作步骤就是,打开开关,设置回调函数,开启通知。

    1. 打开开关
      把异常事件的开关打开。
        caps.can_generate_exception_events = 1;
    
    1. 设置回调函数
      编写回调函数
    void JNICALL jvmti_EventException
            (jvmtiEnv *jvmti_env,
             JNIEnv
             *jni_env,
             jthread thread,
             jmethodID
             method,
             jlocation location,
             jobject
             exception,
             jmethodID catch_method,
             jlocation
             catch_location) {
        char *classSign = nullptr, *classGenericSign = nullptr,
                *methodName = nullptr, *methodSign = nullptr, *methodGenericSign = nullptr,
                *catchMethodName = nullptr, *catchMethodSign = nullptr, *catchMethodGenericSign = nullptr;
    
        //jni打印堆栈
        jclass exceptionClass = jni_env->GetObjectClass(exception);
        jmethodID jmethodId = jni_env->GetMethodID(exceptionClass, "printStackTrace", "()V");
        jni_env->CallVoidMethod(exception, jmethodId);
    
        //获取异常的方法签名
        jvmti_env->GetClassSignature(exceptionClass, &classSign, &classGenericSign);
        //获取throw异常的方法
        jvmti_env->GetMethodName(method, &methodName, &methodSign, &methodGenericSign);
    
        std::cout << classSign << std::endl;
        std::cout << methodName << " " << methodSign << " " << location << std::endl;
    
        //获取catch异常的方法
        jvmti_env->GetMethodName(catch_method, &catchMethodName, &catchMethodSign, &catchMethodGenericSign);
        if (catchMethodName != nullptr) {
            std::cout <<"catch"<< catchMethodName << " " << catchMethodSign << " " << catch_location << std::endl;
        }
    
    
        if (classSign != nullptr) {
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(classSign));
        }
        if (classGenericSign != nullptr) {
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(classGenericSign));
        }
        if (methodName != nullptr) {
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(methodName));
        }
        if (methodSign != nullptr) {
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(methodSign));
        }
        if (methodGenericSign != nullptr) {
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(methodGenericSign));
        }
        if (catchMethodName != nullptr) {
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(catchMethodName));
        }
        if (catchMethodSign != nullptr) {
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(catchMethodSign));
        }
        if (catchMethodGenericSign != nullptr) {
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(catchMethodGenericSign));
        }
    
    }
    

    这里要注意的是最后的回收操作。jvmti的文档中特意声明了,通过jvmti接口产生的对象,需要通过deallocate函数进行释放。

    JVM TI functions always return an error code via the jvmtiError function return value. Some functions can return additional values through pointers provided by the calling function.In some cases, JVM TI functions allocate memory that your program must explicitly deallocate. This is indicated in the individual JVM TI function descriptions. Empty lists, arrays, sequences, etc are returned as NULL.

    设置回调

        cb.Exception = &jvmti_EventException;
    
    1. 开启通知
      jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, NULL);
    

    测试用例

        public static void main(String[] args) throws InterruptedException {
            new InterruptedException("test");
            try {
                testException();
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    
        public static void testException() {
            throw new RuntimeException("hello");
        }
    
    

    测试用例很简单。主要是测试直接new异常等会不会触发,多次捕获,throw的结果。

    
    Ljava/lang/RuntimeException;
    testException ()V 9
    catchmain ([Ljava/lang/String;)V 16
    
    Ljava/lang/Error;
    main ([Ljava/lang/String;)V 25
    
    java.lang.RuntimeException: hello
        at com.company.Main.testException(Main.java:20)
        at com.company.Main.main(Main.java:10)
    java.lang.Error: java.lang.RuntimeException: hello
        at com.company.Main.main(Main.java:12)
    Caused by: java.lang.RuntimeException: hello
        at com.company.Main.testException(Main.java:20)
        at com.company.Main.main(Main.java:10)
    Exception in thread "main" java.lang.Error: java.lang.RuntimeException: hello
        at com.company.Main.main(Main.java:12)
    Caused by: java.lang.RuntimeException: hello
        at com.company.Main.testException(Main.java:20)
        at com.company.Main.main(Main.java:10)
    

    可以看到只是创建的InterruptedException是没有获取到的。Error只有throw没有catch。RuntimeException在throw,catch都获取到了。

    相关文章

      网友评论

        本文标题:利用jvmti查看java异常

        本文链接:https://www.haomeiwen.com/subject/hapfyhtx.html