为了账号安全,请及时绑定邮箱和手机立即绑定

利用jvmti查看java异常

标签:
Java

日常监控中,异常信息,可以说是一个常见的指标,但是有些时候,因为使用不当(线程池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都获取到了。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
1.6万
获赞与收藏
380

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消