基于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 体系概览

目录
相关文章
|
3月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
108 1
|
3月前
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
175 13
|
3月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
63 1
|
2天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
35 14
|
5天前
|
缓存 Java 应用服务中间件
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
27 5
|
1月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
3月前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
3月前
|
SQL Java
使用java在未知表字段情况下通过sql查询信息
使用java在未知表字段情况下通过sql查询信息
54 8
|
3月前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
120 8
|
3月前
|
Java 编译器
如何避免在 Java 中出现 NoSuchElementException 异常
在Java中,`NoSuchElementException`通常发生在使用迭代器、枚举或流等遍历集合时,尝试访问不存在的元素。为了避免该异常,可以在访问前检查是否有下一个元素(如使用`hasNext()`方法),或者使用`Optional`类处理可能为空的情况。正确管理集合边界和条件判断是关键。
131 6

热门文章

最新文章