Java调试那点事

云栖社区 2017-01-14 22:40

点击上方“公众号”可以订阅哦 Java调试概述 程序猿都调试或者debug过Java代码吧?都体会过被PM,PD,测试,业务同学们围观debug吧?说调试,先看'...

点击上方“公众号”可以订阅哦

Java调试概述

程序猿都调试或者debug过Java代码吧?都体会过被PM,PD,测试,业务同学们围观debug吧?说调试,先看看调试严格定义是什么。引用Wikipedia定义:

调试(De-bug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。调试的基本步骤:

1. 发现程序错误的存在

2. 以隔离、消除的方式对错误进行定位

3. 确定错误产生的原因

4. 提出纠正错误的解决办法

5. 对程序错误予以改正,重新测试

用调试的好处是我们就无需每次新测试都要重新编译了,不用copy-paste一堆的System.out.println(很low但很多时候很管用有没有?)。

更多时候我们调试最直接简单的办法就是IDE,Java程序员用的最多的必然是Eclipse,Netbeans和IntelliJ也有各自忠实的粉丝,各有优劣。关于用IDE如何调试可以另起一个话题再讨论。

除了IDE之外,JDK也自带了一些命令行调试工具也很方便。大家用的比较多的如下表所示:

具体用法可以参考JDK文档,这些大家在线上调试应用的时候用的也不少,比如一般线上load高的问题排查步骤是:

1.先用top找到耗资源的进程

2.ps+grep找到对应的java进程/线程

3.jstack分析哪些线程阻塞了,阻塞在哪里

4.jstat看看FullGC频率

5.jmap看看有没有内存泄露

但这个也不是今天的重点,那么问题来了(blue fly is the strongest):这些工具如何能获取远程Java进程的信息的?又是如何远程控制Java进程的运行的? 相信有不少人和我一样对这些工具的 实现原理 很好奇,本文就尝试介绍下各中缘由。

Java调试体系JPDA简介

Java虚拟机设计了专门的API接口供调试和监控虚拟机使用,被称为Java平台调试体系即Java Platform Debugger Architecture(JPDA)。JPDA按照抽象层次,又分为三层,分别是

JVM TI - Java VM Tool Interface

虚拟机对外暴露的接口,包括debug和profile

JDWP - Java Debug Wire Protocol

调试器和应用之间通信的协议

JDI - Java Debug Interface

Java库接口,实现了JDWP协议的客户端,调试器可以用来和远程被调试应用通信

用一个不是特别准确但是比较容易理解的类比,大家可以和HTTP做比较,可以推断他就是一个典型的C/S应用,所以也可以很自然的想到,JDI是用TCP Socket和虚拟机通信的,后面会详细再介绍。

IDE+JDI = 浏览器

JDWP = HTTP

JVMTI = RESTful接口

Debugee虚拟机= REST服务端

和其他的Java模块一样,Java只定义了Spec规范,也提供了参考实现(Reference Implementation),但是第三方完全可以参照这个规范,按照自己的需要去实现其中任意一个组件,原则上除了规范上没有定义的功能,他们应该能正常的交互,比如Eclipse就没有用Sun/Oracle的JDI,而是自己实现了一套(由于开源license的兼容原因),因为直接用JDWP协议调用JVMTI是不会受GPL“污染”的。的确有第三方调试工具基于JVMTI做了一套调试工具,这样效率更高,功能更丰富,因为JDI出于远程调用的安全考虑,做了一些功能的限制。用户还可以不用JDI,用自己熟悉的C或者脚本语言开发客户端,远程调试Java虚拟机,所以JPDA真个架构是非常灵活的。

JVMTI

JVMTI是整个JPDA中最中要的API,也是虚拟机对外暴露的接口,掌握了JVMTI,你就可以真正完全掌控你的虚拟机,因为必须通过本地加载,所以暴露的丰富功能在安全上也没有太大问题。更完整的API内容可以参考JVMTI SPEC:

· 虚拟机信息

o 堆上的对象

o 线程和栈信息

o 所有的类信息

o 系统属性,运行状态

· 调试行为

o 设置断点

o 挂起现场

o 调用方法

· 事件通知

o 断点发生

o 异步调用

在JPDA的这个图里,agent是其中很重要的一个模块,正是他把JDI,JDWP,JVMTI三部分串联成了一个整体。简单来说agent的特性有

· C/C++实现的

· 被虚拟机以动态库的方式加载

· 能调用本地JVMTI提供的调试能力

· 实现JDWP协议服务器端

· 与JDI(作为客户端)通信(socket/shmem等方式)

Code speak louder than words. 上个代码加注释来解释:

写好agent后,需要编译,并在启动Java进程时指定加载路径

Agent实现的动态链接库其实有两种加载方式:

· 虚拟机启动初期加载 这个链接库必须实现Agent_作为函数入口。这种方式可以利用的接口和功能更多,因为他在被调式虚拟机运行的应用初始化之前就被调用了,但是限制是必须以显示的参数指定启动方式,这在线上环境上是不大现实的。

· 动态加载 这是更灵活的方式,Java进程可以正常启动,如果需要,通过Sun/Orale提供的私有Attach API可以连上对应的虚拟机,再通过JPDA方式控制,不过因为虚拟机已经开始运行了,所以功能上会有限制。我们比较熟悉的jstack等jdk工具就是通过这种方式做的,动态库必须实现Agent_OnAttach作为函数入口。如果有兴趣理解Attach机制细节的话,可以参考这个blog,简单来说,就是虚拟机默认起了一个线程(没错,就是jstack时看到Signal Dispatcher这货),专门接受处理进程间singal通知,当他收到SIGQUIT时,就会启动一个新的socket监听线程(就是jstack看到的Attach Listener线程)来接收命令,Attach Listener就是一个agent实现,他能处理很多dump命令,更重要的是他能再加载其他agent,比如jdwp agent。

通过Attach机制,我们能自己非常方便的实现一个jinfo或者其他jdk tools,只需通过JPS获取pid,在通过attach api去load我们提供的agent,完整的jinfo例子也在附件里。

JDWP

JDWP 是 Java Debug Wire Protocol 的缩写,它定义了调试器(debugger)和被调试的 Java 虚拟机(debugee)之间的通信协议。他就是同过JVMTI Agent实现的,简单来说,他就是对JVMTI调用(输入和输出,事件)的通信定义。

JDWP 有两种基本的包(packet)类型:命令包(command packet)和回复包(reply packet)。JDWP 本身是无状态的,因此对 命令出现的顺序并不受限制。而且,JDWP 可以是异步的,所以命令的发送方不需要等待接收到回复就可以继续发送下一个命令。Debugger 和 Debugee 虚拟机都有可能发送命令:

· Debugger 通过发送命令获取Debugee虚拟机的信息以及控制程序的执行。Debugger虚拟机通过发送 命令通知 Debugger 某些事件的发生,如到达断点或是产生异常。

最新文章
猜你喜欢