Java的调试体系-JPDA架构
# Java的调试体系-JPDA架构
# 一、前言
在日常工作中,往往会在开发环境或者线上环境遇到一些bug,这个时候,我们会在IDE工具(idea、eclipse)中开启debug模式进行断点调试,又或者使用一些第三方的工具,例如Arthas
进行分析,定位问题。
大家是否有好奇过debug是什么原理?Arthas
底层是通过什么方式实现的?Skywalking
底层又是通过什么方式实现的呢?
# 二、JPDA是什么?
JPDA全称Java Platform Debugger Architecture
,是一个多层的debug架构体系 ,这套体系为开发人员提供了一整套用于调试 Java 程序的 API。
JPDA由三层组成:
Components Debugger Interfaces
/ |--------------|
/ | VM |
debuggee ----( |--------------| <------- JVM TI - Java VM Tool Interface(Java 虚拟机工具接口)
被调试者 \ | back-end |
\ |--------------|
/ |
comm channel -( | <--------------- JDWP - Java Debug Wire Protocol(Java 调试协议)
通信器 \ |
/ |--------------|
/ | front-end |
debugger ----( |--------------| <------- JDI - Java Debug Interface(Java 调试接口)
调试器 \ | UI |
\ |--------------|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. JVM TI-Java 虚拟机工具接口
JVMTI 的功能非常丰富,包含了 虚拟机中线程、内存 / 堆 / 栈,类 / 方法 / 变量,事件 / 定时器处理等等 20 多类功能,从功能上大致可以分为4类,如下:
- Heap:获取所有类信息,对象信息,对象引用关系,Full GC开始/结束,对象回收事件等;
- 线程与堆栈:获取所有线程信息,线程组信息,控制线程(start,suspend,resume,interrupt…), Thread Monitor(Lock),得到线程堆栈,控制出栈,方法强制返回,方法栈本地变量等;
- Class & Object & Method & Field 元信息:class信息,符号表,方法表,redefine class(hotswap), retransform class,object信息,fields信息,method信息等;
- 工具类:线程cpu消耗,classloader路径修改,系统属性获取等;
# 2. JDWP-Java 调试协议
2.1、通信机制 调试器与被调试JVM之间需要通过一定的方式进行通信,通信的机制主要包括两部分
- 连接器(Connector)连接器是指调试器与被调试 JVM 之间的一个连接,JPDA 在 JDI 这一层面实现了连接器,连接器有三种:
- Listening 连接器:调试器监听来自被调试 JVM 的连接;
- Attaching 连接器:调试器连接上一个处于运行状态的被调试 JVM;
- Launching 连接器:调试器直接亲手启动被调试 JVM,此时调试器与被调试代码运行在同一个 JVM 中
- 通信方式(Transport)通信方式是指调试器与被调试 JVM 之间的数据交换方式和通信报文格式,JPDA 在 JDWP 中定义了报文规范。
# 3. JDI-Java 调试接口
# 三、Java agent工作过程?
Java Agent 又叫做 Java 探针,是在 JDK1.5 引入的一种可以动态修改 Java 字节码的技术。在Java 类编译之后形成的字节码被 JVM 执行之前获取这些字节码信息,并且通过字节码转换器对这些字节码进行修改,来完成一些额外的功能。
也可以认为这个功能是 Java 虚拟机提供的一整套后门,通过这套后门可以对虚拟机方方面面进行监控与分析,甚至干预虚拟机的运行。
JVMTI有两种启动方式:启动时载入、运行时载入
# 1. 启动时载入
我们查看java命令的帮助文档java -h
,会发现三个选项agentlib
,agentpath
,javaagent
,我们重点看下javaagent
,其中Skywalking就是通过这种方式载入,如下:
# load native agent library <libname>, e.g. -agentlib:jdwp
# see also -agentlib:jdwp=help
-agentlib:<libname>[=<options>]
# load native agent library by full pathname
-agentpath:<pathname>[=<options>]
# -javaagent:/xxx/xxx/skywalking-agent.jar
# load Java programming language agent, see java.lang.instrument
-javaagent:<jarpath>[=<options>]
2
3
4
5
6
7
8
9
10
启动时载入,处于虚拟机初始化的早期,在这个时间点上:
- 所有的 Java 类都未被初始化;
- 所有的 Java 对象实例都未被创建;
- 因而,没有任何 Java 代码被执行;
但在这个时候,我们已经可以:
- 操作 JVMTI 的 Capability 参数;
- 使用系统参数;
# 2. 运行时载入
通过attach api
,这是一套纯Java的API,它负责动态地将dynamic module attach到指定进程id的Java进程内并触发回调。其中Arthas
就是通过这种方式载入的,例子如下
import java.io.IOException;
import com.sun.tools.attach.VirtualMachine;
public class VMAttacher {
public static void main(String[] args) throws Exception {
// args[0]为java进程id
VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(args[0]);
// args[1]为共享库路径,args[2]为传递给agent的参数
virtualMachine.loadAgentPath(args[1], args[2]);
virtualMachine.detach();
}
}
2
3
4
5
6
7
8
9
10
11
12
本文主要参考文档:
JAVA-JPDA官网文档 (opens new window)、浅谈JPDA中JVMTI模块 (opens new window)
基于Java Instrument的Agent实现 (opens new window)、JAVA进阶之Agent (opens new window)