基于jvmti定位java异常信息

简介: JVMTI(JVM Tool Interface)位于jpda最底层,是Java虚拟机所提供的native编程接口。JVMTI可以提供性能分析、debug、内存管理、线程分析等功能。

背景描述

JVMTI(JVM Tool Interface)位于jpda最底层,是Java虚拟机所提供的native编程接口。JVMTI可以提供性能分析、debug、内存管理、线程分析等功能。

JPDA 定义了一个完整独立的体系,它由三个相对独立的层次共同组成,而且规定了它们三者之间的交互方式,或者说定义了它们通信的接口。这三个层次由低到高分别是 Java 虚拟机工具接口(JVMTI),Java 调试线协议(JDWP)以及 Java 调试接口(JDI)。这三个模块把调试过程分解成几个很自然的概念:调试者(debugger)和被调试者(debuggee),以及他们中间的通信器。被调试者运行于我们想调试的 Java 虚拟机之上,它可以通过 JVMTI 这个标准接口,监控当前虚拟机的信息;调试者定义了用户可使用的调试接口,通过这些接口,用户可以对被调试虚拟机发送调试命令,同时调试者接受并显示调试结果。在调试者和被调试者之间,调试命令和调试结果,都是通过 JDWP 的通讯协议传输的。所有的命令被封装成 JDWP 命令包,通过传输层发送给被调试者,被调试者接收到 JDWP 命令包后,解析这个命令并转化为 JVMTI 的调用,在被调试者上运行。类似的,JVMTI 的运行结果,被格式化成 JDWP 数据包,发送给调试者并返回给 JDI 调用。而调试器开发人员就是通过 JDI 得到数据,发出指令。

39.jpg

JDPA 模块层次.png

模块 层次 编程语言 作用
JVMTI 底层 C 获取及控制当前虚拟机状态
JDWP 中介层 C 定义 JVMTI 和 JDI 交互的数据格式
JDI 高层 Java 提供 Java API 来远程控制被调试虚拟机

Java 虚拟机工具接口(JVMTI)

JVMTI(Java Virtual Machine Tool Interface)即指 Java 虚拟机工具接口,它是一套由虚拟机直接提供的 native 接口,它处于整个 JPDA 体系的最底层,所有调试功能本质上都需要通过 JVMTI 来提供。通过这些接口,开发人员不仅调试在该虚拟机上运行的 Java 程序,还能查看它们运行的状态,设置回调函数,控制某些环境变量,从而优化程序性能。我们知道,JVMTI 的前身是 JVMDI 和 JVMPI,它们原来分别被用于提供调试 Java 程序以及 Java 程序调节性能的功能。在 J2SE 5.0 之后 JDK 取代了 JVMDI 和 JVMPI 这两套接口,JVMDI 在最新的 Java SE 6 中已经不提供支持,而 JVMPI 也计划在 Java SE 7 后被彻底取代。

Java 调试线协议(JDWP)

JDWP(Java Debug Wire Protocol)是一个为 Java 调试而设计的一个通讯交互协议,它定义了调试器和被调试程序之间传递的信息的格式。在 JPDA 体系中,作为前端(front-end)的调试者(debugger)进程和后端(back-end)的被调试程序(debuggee)进程之间的交互数据的格式就是由 JDWP 来描述的,它详细完整地定义了请求命令、回应数据和错误代码,保证了前端和后端的 JVMTI 和 JDI 的通信通畅。比如在 Sun 公司提供的实现中,它提供了一个名为 jdwp.dll(jdwp.so)的动态链接库文件,这个动态库文件实现了一个 Agent,它会负责解析前端发出的请求或者命令,并将其转化为 JVMTI 调用,然后将 JVMTI 函数的返回值封装成 JDWP 数据发还给后端。

Java 调试接口(JDI)

JDI(Java Debug Interface)是三个模块中最高层的接口,在多数的 JDK 中,它是由 Java 语言实现的。JDI 由针对前端定义的接口组成,通过它,调试工具开发人员就能通过前端虚拟机上的调试器来远程操控后端虚拟机上被调试程序的运行,JDI 不仅能帮助开发人员格式化 JDWP 数据,而且还能为 JDWP 数据传输提供队列、缓存等优化服务。从理论上说,开发人员只需使用 JDWP 和 JVMTI 即可支持跨平台的远程调试,但是直接编写 JDWP 程序费时费力,而且效率不高。因此基于 Java 的 JDI 层的引入,简化了操作,提高了开发人员开发调试程序的效率。

开发简述

基于jvmti提供的接口服务,运用C++代码(win32-add_library)在Agent_OnLoad里开发监控服务,并生成dll文件。开发完成后在java代码中加入agentpath,这样就可以监控到我们需要的信息内容。

环境准备

1、Dev-C++

2、JetBrains CLion 2018.2.3

3、IntelliJ IDEA Community Edition 2018.3.1 x64

4、jdk1.8 64位

5、jvmti(在jdk安装目录下jdk1.8.0_45\include里,复制到和工程案例同层级目录下)

配置信息

(路径相关修改为自己的)

1、C++开发工具Clion配置

1.1、配置位置;Settings->Build,Execution,Deployment->Toolchains

1.2、MinGM配置:D:\Program Files (x86)\Dev-Cpp\MinGW64

2、java调试时配置

2.1、配置位置:Run/Debug Configurations ->VM options

2.2、配置内容:-agentpath:E:\itstack\itstack.org\demo\jvmti\itstack-demo-jvmti-dll\cmake-build-debug\libitstack_demo_jvmti_dll.dll

代码示例

c++ 代码块:

1#include <iostream>
 2#include <cstring>
 3#include "jvmti.h"
 4
 5using namespace std;
 6
 7//异常回调函数
 8static void JNICALL callbackException(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thr, jmethodID methodId, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location) {
 9
10    // 获得方法对应的类
11    jclass clazz;
12    jvmti_env->GetMethodDeclaringClass(methodId, &clazz);
13
14    // 获得类的签名
15    char *class_signature;
16    jvmti_env->GetClassSignature(clazz, &class_signature, nullptr);
17
18    //过滤非本工程类信息
19    string::size_type idx;
20    string class_signature_str = class_signature;
21    idx = class_signature_str.find("org/itstack");
22    if (idx != 1) {
23        return;
24    }
25
26    //异常类名称
27    char *exception_class_name;
28    jclass exception_class = env->GetObjectClass(exception);
29    jvmti_env->GetClassSignature(exception_class, &exception_class_name, nullptr);
30
31    // 获得方法名称
32    char *method_name_ptr, *method_signature_ptr;
33    jvmti_env->GetMethodName(methodId, &method_name_ptr, &method_signature_ptr, nullptr);
34
35    //获取目标方法的起止地址和结束地址
36    jlocation start_location_ptr;    //方法的起始位置
37    jlocation end_location_ptr;      //用于方法的结束位置
38    jvmti_env->GetMethodLocation(methodId, &start_location_ptr, &end_location_ptr);
39
40    //输出测试结果
41    cout << "测试结果-定位类的签名:" << class_signature << endl;
42    cout << "测试结果-定位方法信息:" << method_name_ptr << " -> " << method_signature_ptr << endl;
43    cout << "测试结果-定位方法位置:" << start_location_ptr << " -> " << end_location_ptr + 1 << endl;
44    cout << "测试结果-异常类的名称:" << exception_class_name << endl;
45
46    cout << "测试结果-输出异常信息(可以分析行号):" << endl;
47    jclass throwable_class = (*env).FindClass("java/lang/Throwable");
48    jmethodID print_method = (*env).GetMethodID(throwable_class, "printStackTrace", "()V");
49    (*env).CallVoidMethod(exception, print_method);
50
51}
52
53JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
54    jvmtiEnv *gb_jvmti = nullptr;
55    //初始化
56    vm->GetEnv(reinterpret_cast<void **>(&gb_jvmti), JVMTI_VERSION_1_0);
57    // 创建一个新的环境
58    jvmtiCapabilities caps;
59    memset(&caps, 0, sizeof(caps));
60    caps.can_signal_thread = 1;
61    caps.can_get_owned_monitor_info = 1;
62    caps.can_generate_method_entry_events = 1;
63    caps.can_generate_exception_events = 1;
64    caps.can_generate_vm_object_alloc_events = 1;
65    caps.can_tag_objects = 1;
66    // 设置当前环境
67    gb_jvmti->AddCapabilities(&caps);
68    // 创建一个新的回调函数
69    jvmtiEventCallbacks callbacks;
70    memset(&callbacks, 0, sizeof(callbacks));
71    //异常回调
72    callbacks.Exception = &callbackException;
73    // 设置回调函数
74    gb_jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
75    // 开启事件监听(JVMTI_EVENT_EXCEPTION)
76    gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr);
77    return JNI_OK;
78}
79
80JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
81}

Java代码块

1package org.itstack.demo.jvmti;
 2import java.util.logging.Logger;
 3
 4public class TestLocationException {
 5
 6    public static void main(String[] args) {
 7        Logger logger = Logger.getLogger("TestLocationException");
 8        try {
 9            User resource = new User();
10            Object obj = resource.queryUserInfoById(null);
11            logger.info("测试结果:" + obj);
12        } catch (Exception e) {
13            //屏蔽异常
14        }
15    }
16}
17
18class User {
19    Logger logger = Logger.getLogger("User");
20    public Object queryUserInfoById(String userId) {
21        logger.info("根据用户Id获取用户信息" + userId);
22        if (null == userId) {
23            throw new NullPointerException("根据用户Id获取用户信息,空指针异常");
24        }
25        return userId;
26    }
27}

测试结果

1四月 13, 2019 12:21:45 下午 org.itstack.demo.jvmti.User queryUserInfoById
 2信息: 根据用户Id获取用户信息null
 3测试结果-定位类的签名:Lorg/itstack/demo/jvmti/User;
 4测试结果-定位方法信息:queryUserInfoById -> (Ljava/lang/String;)Ljava/lang/Object;
 5测试结果-定位方法位置:0 -> 43
 6测试结果-异常类的名称:Ljava/lang/NullPointerException;
 7测试结果-输出异常信息(可以分析行号):
 8java.lang.NullPointerException: 根据用户Id获取用户信息,空指针异常
 9    at org.itstack.demo.jvmti.User.queryUserInfoById(TestLocationException.java:23)
10    at org.itstack.demo.jvmti.TestLocationException.main(TestLocationException.java:10)

其他内容:

1、jvmti api

2、JPDA 体系概览

目录
相关文章
|
12天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
12天前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
|
13天前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
|
14天前
|
Java 编译器
如何避免在 Java 中出现 NoSuchElementException 异常
在Java中,`NoSuchElementException`通常发生在使用迭代器、枚举或流等遍历集合时,尝试访问不存在的元素。为了避免该异常,可以在访问前检查是否有下一个元素(如使用`hasNext()`方法),或者使用`Optional`类处理可能为空的情况。正确管理集合边界和条件判断是关键。
|
15天前
|
人工智能 监控 数据可视化
Java智慧工地信息管理平台源码 智慧工地信息化解决方案SaaS源码 支持二次开发
智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台,是一种全新的管理模式,能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度,以及施工过程管理的进度、质量、安全三大体系为基础应用,实现全面高效的工程管理需求,满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效,为监管平台提供数据支撑。
32 3
|
17天前
|
Java
Java异常捕捉处理和错误处理
Java异常捕捉处理和错误处理
14 1
|
19天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
40 2
|
26天前
|
Java
如何在 Java 中处理“Broken Pipe”异常
在Java中处理“Broken Pipe”异常,通常发生在网络通信中,如Socket编程时。该异常表示写入操作的另一端已关闭连接。解决方法包括:检查网络连接、设置超时、使用try-catch捕获异常并进行重试或关闭资源。
|
28天前
|
存储 安全 Java
如何避免 Java 中的“ArrayStoreException”异常
在Java中,ArrayStoreException异常通常发生在尝试将不兼容的对象存储到泛型数组中时。为了避免这种异常,确保在操作数组时遵循以下几点:1. 使用泛型确保类型安全;2. 避免生类型(raw types)的使用;3. 在添加元素前进行类型检查。通过这些方法,可以有效防止 ArrayStoreException 的发生。
|
29天前
|
人工智能 Oracle Java
解决 Java 打印日志吞异常堆栈的问题
前几天有同学找我查一个空指针问题,Java 打印日志时,异常堆栈信息被吞了,导致定位不到出问题的地方。
34 2
下一篇
无影云桌面