龙空技术网

底层技术揭秘:java调试工具实现原理与使用技巧

Java编程技术 1056

前言:

此时你们对“javadebug”大约比较注意,兄弟们都想要知道一些“javadebug”的相关知识。那么小编同时在网上收集了一些有关“javadebug””的相关知识,希望姐妹们能喜欢,看官们一起来学习一下吧!

原创声明

本公众号所有文章均原创,未经许可不得转载。如果喜欢我们的文章,欢迎关注哦!

文 吴潇/Java高级工程师

java 技术栈的程序员大多使用过远程调试。如果你还没有用过java远程调试,请仔细看一看本篇文章第一小节,查问题效率立即提升数倍;对于使用过java远程调试的老手来说,有没有想过它的底层是怎么实现的呢?今天这篇文章就来揭秘(程序员应该了解自己每天使用的工具,磨炼自己的技艺)

1. Java远程调试基本操作

Java进程默认不支持远程调试,如果需要远程调试,必须在启动java之前加上特定选项。

第一步、在启动JVM的时候,加上以下调试选项:

Java 5以前:

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1044Java 5及以后:

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

两者之间有啥区别

“-Xdebug -Xrunjdwp” 主要用于Java 5以前。其中,“-Xdebug”是让JVM开启调试支持,开启调试功能就会要求jvm运行于解释执行模式,因此,java程序的执行速度就变得非常非常慢;“-Xrunjdwp”是让JVM运行一个JDWP协议,从而允许远程调试。

-agentlib:jdwp 用于Java 5及以后,开启后JVM运行于JIT模式,速度更快。因为Java 5采用了HotSpot VM,增加了动态反优化技术,使得调试速度更快。通过这个选项长期开启调试支持也不会影响程序运行速度。同时还支持热交换技术,使得在调试的过程中可以修改Class的代码,从而更快速地定位到问题。

以上两种方式,都需要进一步配置远程调试参数,即“runjdwp:”和“jdwp=”后面的选项。选项具体配置如下。

第二步、在IntelliJ IDEA中,新建一个远程调试的运行配置(Run configuration):

第三步、点击调试按钮,就可以进行远程调试了。

注意要满足以下条件:

要确保本地代码与远程代码一致;被调试的程序在编译的时候,加上了调试信息的(命令:javac -g ...)2. IntelliJ IDEA 或 Eclipse 如何实现调试功能的

有了远程调试经验的老手们可能会遇到一些实际问题。例如,有的bug在线上能够稳定复现,但是当我们对目标程序进行调试的时候,这个bug竟然神奇般地消失了。你有遇到过这样的问题吗?我就遇到过这样的问题。正是我遇到的这个诡异现象促使我全面了解了一遍java远程调试到底是怎么实现的,它对目标程序自身有什么影响?

比如,我们在debug一段程序的时候,需要设置断点,然后再单步执行。当程序运行到某个断点位置时,这个线程就会被suspend,等待我们的debugger告诉目标程序,下一步要怎么执行,是step还是resume还是run to cursor等。假如我们正在单步执行,又有新线程命中这个断点,那么这个线程是会继续执行还是会suspend呢?我们在debug的过程中,线程被暂停,GC又是怎么发生的,也被暂停了吗?线程被我们的断点暂停后,跟时间相关的代码会把我们调试导致的暂停的时间也包含在内吗?诸如此类的问题,要想弄明白,都需要我们深入了解java远程调试这个底层技术。

3. Java Platform Debugger Architecture (JPDA)

JPDA是Java实现调试功能的架构设计,主要目的是提供给工具开发商开发调试工具 (debugger application)。JPDA保证了调试工具可跨平台、跨JVM、跨JDK版本运行。

JPDA是分层设计的,总共分3层:

Java VM Tool Interface (JVM TI): Java虚拟机工具接口层,位于JPDA最底层,负责定义由JVM提供的调试服务的接口,而JVM负责实现JVM TI接口。可见,Java程序的调试也是按照SPI(Service Provier Interface)设计模式设计的,把JVM的调试能力看作一种服务,通过接口来对外提供这种服务,而JVM负责调试功能的具体实现。Java Debug Wire Protocol (JDWP):负责被调试的进程和debugger前端之间的通信。JVM TI提供的调试服务是面向native语言的(C/C++),主要供JVM进程内部调用,因此需要通过JDWP来实现跨进程调试。Java Debug Interface (JDI):面向Java语言的最高层接口,供工具开发商快速开发debugger程序,实现远程调试。JDK推荐使用JDI开发debugger,从而获得java技术栈的跨平台运行能力。

JPDA架构图

debuggee: 被调试进程,包含正在被调试的应用、运行应用的VM、debugger后端Java Virtual Machine: java的调试功能最终是由VM负责实现的。back-end: 主要职责是通信,把调试请求从debugger前端发送到VM,再把response返回回去。debugger后端与前端通信是基于JDWP协议。communication channel: debugger后端与debugger前端之间的连接,包含2个组件。connector: 通过connector建立连接。connector分三类:listening connector, attaching connector, launching connectortransport: 负责底层数据交换,可以用的transport有sockets, serial lines, 和 shared memory。front-end: 负责实现JDI接口

JPDA只是定义了接口规范 ,需要具体实现,才能实现java调试。

Oracle JDK 针对这3个接口,提供了参考实现,具体包含:

在VM上实现了JVM TI 接口一个debugger back-end 实现(使用了JVM TI接口,实现了JDWP协议的debuggee一端)一个debugger front-end 实现(使用了JDWP协议的debugger一端的功能,实现了JDI接口)2个简单的基于JDI的调试工具,即jdb。

JDK提供的参考实现

JPDA是分层架构设计,其中的每一层都是可以替换的。调试工具可以基于JDI开发,也可以基于JDWP开发,或直接基于JVM TI开发。

Java中的调试功能的实现步骤

VM提供调试服务,例如提供API用于设置断点,取消断点,设置watch point,单步执行、查看stack frame等。VM把调试功能封装成API,通过JVM TI接口的形式提供给调用方。debugger后端程序通过调用JVM TI获得调试信息或设置调试动作,然后按照JDWP协议进行封装,实现远程调试。debugger前端按照JDWP协议调用debugger后端功能,把功能封装成JDI接口。调试工具,例如IntelliJ IDEA中的debugger,直接调用JDI实现调试功能。4. Java虚拟机工具接口 (JVM TI)

JVM TI (Java Virtual Machine Tool Interface) 是一个本地编程接口(C++),主要给工具开发商使用,例如像Eclipse、IntelliJ IDEA这样的工具。JVM TI 可以实现的功能有:

查看Java应用程序内部状态控制Java应用程序的执行查看JVM的内部状态

通过用JVM TI来访问JVM的内部状态,可以实现各种各样的工具,例如 profiling(分析)、debugging(调试)、monitoring(监控)、thread analysis(线程分析) 和 coverage analysis等类型工具。

JVM的调试功能是在JVM内部实现,然后通过JVM TI接口开放给第三方工具厂商的。

JVM TI是一个双向接口。JVM TI的客户端(也就是agent)可以利用events功能感知到JVM中发生的事件,还可以通过functions功能来查询程序的状态和控制程序的执行。

这个agent与JVM在同一个进程中运行,它通过直接调用JVM TI接口与JVM通信。agent再被另外一个进程控制,这样实现java本地调试功能。

在windows系统上,agent一般是一个DLL库,在类unix系统上,agent一般是一个so文件。

agent是在JVM启动的时候,通过命令行选项加载的,有2种加载方式:

加载方式一

-agentlib:<agent-lib-name>=<options>brbragent-lib-name 指的是agent的名称broptions是传给这个agents的选项

加载方式二

-agentpath:<path-to-agent>=<options>

JVM TI 过于靠近底层,大部分工具是通过JPDA间接访问JVM TI的。

5. 远程调试通信协议:Java Debug Wire Protocol (JDWP)

JDWP是一种实现Java应用程序远程调试的通信协议,即jvm和debugger之间实现通信,例如传输线程状态、异常信息等。

JDWP实现debuggee和debugger之间的隔离,所以debugger可跨平台运行和调试。

隔离除了可以实现跨平台的优势以外,被调试的JVM中的GC、OOM等事件不会影响debugger自身运行。

JDWP只定义数据的格式,没有指定传输协议,只需一个简单API就可以支持多种传输方式。

JDWP简单,容易实现,灵活,便于扩展(JDWP设计考虑到了JDI的使用方便)。

JDWP Start Up(握手机制)

建立连接之后,首先要经过一次握手,然后才开始发送packet。

debugger → target vm: JDWP-Handshake

target vm → debugger: JDWP-Handshake

JDWP Packet (数据包)

packet分为2类:1. command packets 2. reply packets

debugger往target vm发送command packet,从而:1)获取信息 2)控制程序执行

target vm往debugger发送command packet,从而:通知debugger,vm中发生的事件(event)。

replay packet 用于通知操作是否成果和获取返回数据

Command Packet

Reply Packet

Headerlength (4 bytes)id (4 bytes)flags (1 byte)command set (1 byte)command (1 byte)data (Variable)Headerlength (4 bytes)id (4 bytes)flags (1 byte)error code (2 bytes)data (Variable)6. Java调试接口:Java Debug Interface (JDI)

JDI是JPDA架构中最高层Java API。通过调用JDI接口可以获得debugger需要的信息,也可以控制被调试程序的运行。

JDK通过SPI模式为JDI提供了具体实现(位于 lib/tools.jar),完整实现了JDWP协议后端部分,可以远程连接、Attach、监控和控制target JVM。

JDI组成部分

name

desc

com.sun.jdi

core package,定义了value, type 和 虚拟机的镜像

com.sun.jdi.connect

提供debugger和target vm之间的Connector实现,实现网络连接

com.sun.jdi.connect.spi

如果JDK自带的Connector实现不能满足需要,可以通过这里的接口自定义Connector

com.sun.jdi.event

JDI中的event定义和处理工具

com.sun.jdi.request

用于实现满足特定条件就发送event

基于JDI开发debugger步骤

1. 调用JDI中用于建立与target vm建立连接的API,创建Connector。本地调试可以创建LaunchingConnector,远程调试可以创建AttachingConnector。

2. Connector创建好之后launch或者attach到target vm。

3. 设置event,例如ClassPrepareEvent、BreakpointRequest等:

例如设置断点可以创建BreakpointRequest

4. 断点触发之后,需要处理BreakpointEvent,例如打印所有变量

5. 综合起来

这个例子来自:

7. 基于JDI开发debugger的示例

JDK的一个子目录(JDK demo包)即 $JDK/demo/jpda 中,有3个JDI接口的使用示例(源码和文档)。

trace:显示程序执行轨迹。jdb:JDK中自带的命令行调试工具。javadt:一个简单的GUI调试工具。

希望这篇文章能对您有帮助!如果您对互联网、前/后/客户端、架构/分布式/高可用/高并发/高实时、电商、Redis、MySQL、Zookeeper、Spring、Android、浏览器插件、Java、C/C++、Linux、个性化推荐、社区发现、机器学习、数据挖掘等感兴趣,欢迎关注。

标签: #javadebug