写点什么

Java 调试技术 JPDA 架构解读,图文详解

用户头像
极客good
关注
发布于: 刚刚
  • JDWP


当想要利用 JVMTI 让 JVM 做一些事的时候,那么就要先要与 Agent 通信,由它代为传话。因此,JVMTI Agent 内置了一个称之为“通信后端”的模块,用来接收外部的请求。


想要与 JVMTI Agent 通信的第三方,就要先与通信后端通信,通信就意味着必然有一个通信协议的存在,这个协议就是 JDWP(Java Debug Wire Protocol) 协议。


  • JDI


Java 程序员经常使用的 eclipse、idea 这样的 IDE 来 debug 程序的时候,就是以 JDWP 协议与目标 JVM 的 JVMTI Agent 通信的。考虑到 JDWP 协议的实现比较繁琐,Java 官方也在 com.sun.jdi 这个 package 中实现了一个叫做 JDI(Java Debug Interface)基础库,JDI 实现了 JDWP 协议,将与 JVMTI Agent 通信的细节封装为一个又一个 Java API,方便第三方与 JVMTI Agent 通信,与 JVMTI Agent 的通信后端相对应,JDI 包含了一个通信前端模块,负责 JDWP 协议的转换以及消息的发送和接收。

三层模型

JPDA 抽象机制设计为三层:


  1. 第一层:调试方,由 JDI 定义调试方的 API

  2. 第二层:通信层,由 JDWP 定义通信协议规范

  3. 第三层:被调试方,JVMTI 定义如何与目标 JVM 交互



为什么 JPDA 机制需要设计层三层呢?原因有几个:


  1. 调试方可能在远程进行调试,而 JDWP 协议又是一个非常底层的二进制协议,实现起来需要花费大量的成本。所以通过 JDI 对 JDWP 协议进行实现,并对外提供 API。

  2. JDI 不仅仅是实现了 JDWP 协议那么简单,它还实现了队列、缓存、连接初始化等等服务,这些服务都可以简单地通过 JDI 的 API 来使用。


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


有了 JVMTI,调试就可以与具体的 JVM 解耦,不同类型的 JVM 只要遵循 JVMTI 规范即可,JDWP 不需要假设它正在与某种类型的 JVM 通信。

集成选择

我们可以不通过 JDI,直接自己实现 JDWP 协议与 JVMTI 通信吗,当然可以。


我们可以不通过 JDWP 协议,直接在 JVM 进程中,编写本地 C/C++ 代码与 JVMTI Agent,或者自己实现一个 JVMTI Agent 与目标 JVM 通信吗?当然也是可以的。


这都要从需求出发:


  1. 如果我们只需要实现一个调试器,例如 IDE,那么我们直接使用 JDI 即可。

  2. 如果我们的调试器不是用 Java 语言写的,那么,我们需要自行实现 JDWP 协议。

  3. 如果 JDI/JDWP 所包含的功能不满足我们的需求,例如堆栈分析,那么我们可以直接通过 JVMTI 来实现我们想要的功能。


JVMTI 规范所对应的功能是最完整的,而 JDWP 仅支持部分功能,JDI 则仅支持调试相关的功能。在功能上,是父子集的关系。

通信机制

调试器与被调试 JVM 之间需要通过一定的方式进行通信,通信的机制主要包括两部分


  1. 连接器(Connector)

  2. 通信方式(Transport)


连接器是指调试器与被调试 JVM 之间的一个连接,JPDA 在 JDI 这一层面实现了连接器。


通信方式是指调试器与被调试 JVM 之间的数据交换方式和通信报文格式,JPDA 在 JDWP 中定义了报文规范。


连接器


连接器有三种:


  1. Listening:调试器监听来自被调试 JVM 的连接;

  2. Attaching:调试器连接上一个已经处于运行状态的被调试 JVM;

  3. Launching:调试器直接亲手启动被调试 JVM,此时调试器与被调试代码实际上是运行在同一个 JVM 中的;


通信方式


调试器与被调试 JVM 之间的数据交换方式,有两种:


  1. 基于 Socket 网络连接,主要用于远程调试,即调试器和被调试 JVM 不在同一台机器上;



  1. 基于操作系统共享内存的通信,主要用于调试器和被调试 JVM 在同一台机器上的情况;


配置

调试器和被调试 JVM 在启动的时候, 都需要通过设置 JVM 参数来让它具有调试的能力或者可被调试的能力。


对于 JDK5 及以上的版本,参数格式为:-agentlib:jdwp={子配置项}


对于 JDK5 以前的版本,参数格式为:-Xdebug 以及 -Xrunjdwp:{子配置项}


而子配置项,包括:


  1. transport:数据交换方式,可选:dt_socketdt_shmem,分别代表 socket 网络通信和共享内存通信

  2. Address:标识一个对端的地址,格式为:{ip}:{port}

  3. server:标识自己是调试者还是被调试者,调试者配置为:n,被调试着配置为:y

  4. suspend:只有被调试者才需要配这个参数,当配置为 y 的时候,代表等待调试者连接上来才真正启动 Java 应用;配置为 n 时,则直接启动 Java 应用。


这里的 Java 应用,是相对于 JVM 来说的,假如把 JVM 看成一个平台,那我们写的代码就是一个 Java 应用。JVM 已经启动,但我们的应用代码还没有跑起来,这种情况在上文的语境中,我们叫做 Java 应用还没启动。


配置示例:


  1. 被调试者开启远程调试监听:


-agentlib:jdwp=transport=dt_socket,address=localhost:7007,server=y,suspend=y


复制代码


  1. 被调试者开启本地共享内存调试监听:


-agentlib:jdwp=transport=dt_shmem,server=y,suspend=n


复制代码


  1. 调试者远程连接被调试者:


-agentlib:jdwp=transport=dt_socket,address=localhost:7007,server=n,suspend=y


复制代码


  1. 调试者基于共享内存方式连接被调试者:


-agentlib:jdwp=transport=dt_shmem, address=<mysharedmem>


复制代码


  1. 调试者基于共享内存方式启动被调试者:


-agentlib:jdwp=transport=dt_shmem,server=y,onuncaught=y,launch=d:\bin\debugstub.exe


复制代码


被调试者基于共享内存的监听启动后,共享内存地址将会打印到控制台上。调试者配置时需要配置这个共享内存的地址


JDI



功能

  1. 提供了跟调试相关的 Java API;

  2. 能够获取一个正在运行的 JVM 的状态,包括:类,数组,接口,基本类型以及这些类型的对象数量;

  3. 与执行相关的控制,例如暂停和恢复线程;

  4. 设置断点,监听异常的发生、类加载、线程创建等;

  5. 提供不同的连接器实现,例如基于 socket 的远程连接器和基于共享内存的本地连接器;

技术架构


  1. 提供事件机制

  2. 对 JDWP 协议的编解码

用法

要使用 JDI 的功能,需要依赖 JDK 自带的 tools.jar 这个工具包,JDI 相关的代码处于 com.sun.jdi 这个包下面。


一个大致的使用步骤如下所示:


  1. 获取一个 VirtualMachine 实例

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
Java 调试技术 JPDA 架构解读,图文详解