IDEA使用debug断点调试技巧详解(附带gif动图演示)

3,731 阅读7分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第n篇文章,点击查看活动详情

前言

        使用debug断点调试是每个程序员的必会技能,我们每天的工作不就是写bug,找别人的bug嘛!debug作为排查问题的一大绝杀技,断点一打,问题分分钟解决。再加上一些IDE的debug工具这么好用,jdk自带的jdb工具也能支持本地调试和远程调试。妥妥的年度最佳工具,但是就我而言,平时排查问题就是打了个行断点,条件断点,虽然也能排查出大部分问题,但是总感觉使用的不够 优雅~,而且对debug的过程也不清楚。

        本文就来了解一下debug的原理和调试技巧。附带git动图演示,文中不会指出基础的debug操作,只列出一些使用技巧,相信你掌握了这些技巧,对debug的理解更进一步,排查解决问题的效率也必然上一个档次。

debug原理概述

        JAVA调试器的运行,是由JPDA(Java Platform Debugger Architecture)体系支撑起来的。引用官方文档中的阐述,JPDA 是一种多层调试架构,开发人员可以创建调试器程序,而且这些调试程序可以跨平台、跨虚拟机、跨JDK版本运行。参考oracle的 官方文档

JPDA is a multi-tiered debugging architecture that allows tools developers to easily create debugger applications which run portably across platforms, virtual machine (VM) implementations and JDK versions.

整个JPDA体系架构如下图所示,分为三个部分:

image.png

  1. JVM TI(Java VM 工具接口) 是 J2SE 5.0 中引入的新接口,它取代了JVMDI。由C语言编写,定义了VM提供的调试服务接口,用于获取和控制当前虚拟机状态。
  2. JDWP (Java 调试线协议) 由C语言编写,定义了被调试者和调试器进程之间的通信数据格式。
  3. JDI ( Java 调试接口) 定义一个高级 Java 语言接口,调试工具的开发人员使用它来编写调试器程序。IDEA的debug工具就是实现了这一套API,才为我们提供出来好用的工具。

        Java远程调试的原理是两个VM之间使用JDWP协议通过socket进行通信,以达到远程调试的目的,当我们在IDEA中以debug模式启动运行类,就可以直接调试了,过程如下:

  1. IDEA客户端和vm程序端 建立 socket 连接。使用JDWP协议。
  2. 将断点位置创建了断点事件通过 JDI 接口传递给服务端VM,VM调用suspend将VM挂起。
  3. VM 挂起之后将客户端需要获取的 VM 信息返回给客户端,返回之后 VM 恢复运行状态
  4. 客户端获取到 VM 返回的信息之后可以通过不同的方式展示给使用者。

        下面通过一个动图,看一下IDEA以debug模式运行程序都做了哪些事,首先建立起双向的通信,应用程序通过debug模式运行起来之后,可以通过jps看到启动参数中多了一行,这就是声明了使用jdwp协议,以及一些vm参数。

-agentlib:jdwp=transport=dt_socket,address=127.0.0.1:**55951**,suspend=y,server=n

iShot_2022-09-25_18.06.54.gif

        其实远程debug也需要加上这段参数,如何进行远程debug会在最后一个章节中提到,接下来列出一下常用的debug技巧。

断点技巧

字段断点

        如果想要知道某个类的字段属性在哪一行代码被修改了值,就需要使用到字段监视断点。需要注意的是字段监视断点只能打在类字段上,打在引用类型的对象或者集合类型上是不生效的。我们也可以为引用类型的对象属性添加watch监视。

  • 字段监视断点可以帮助我们定位到在哪一行被修改了。
  • 属性watch可以帮助我们捋清楚变量值的变化过程。

        如下图所示,我们为变量a上打了一个字段监视断点,debug运行时,则会停在改变a值的哪一行,我们也可以为这个a值添加watch,以观察这个值变化链上的每一次值改变状况。

fieldWatch (1).gif

方法断点

        当接口有多个实现时,当不知道程序执行走的是哪一个类的实现时,就可以将断点打在接口中的方法上,那么程序运行后可以直接步入到具体的实现类中的方法。

iShot_2022-09-25_11.48.48.gif

        有时候不想在方法里一步步执行,可以直接在方法签名上打断点,断点会在方法的首尾行停留,配合上watch,我们可以看这个方法中究竟做了什么,以及产生了哪些影响。

iShot_2022-09-25_11.54.18.gif

条件断点

        常用于多线程或者请求频繁时,当不断有请求调用当前方法时,我们可以设置条件断点,满足条件时,才会步入断点行,然后对当前请求进一步调试。条件断点怎么打就不用多说了,右键断点,添加条件判断语句,只有true和false两种结果,条件是根据上下文写的。

异常断点

        异常断点在工作中也非常有用,就是因为遇到了异常才去调试程序,那怎么能不用上异常断点呢?平时看到长串的异常堆栈信息,例如最常见的空指针异常,我们都是找到对应是哪一行爆出来抛出的异常,然后慢慢往下跟,找到具体为null的变量,才能找出导致程序退出的根因。

        IDEA的debug工具,提供的异常断点,顾名思义,就是当遇到异常的时候,断点会停在当前行,使用起来也非常简单,只需要添加进对应的异常类型,开启异常断点即可。如下图在空指针异常时断点。以供查看上下文变量的值。

iShot_2022-09-25_16.06.38.gif

多线程断点

        当遇到多线程调试的时候也是个麻烦事儿,因为不知道当前到底是哪一个线程在运行中,所以就需要使用到线程debug了,也是结合条件断点,根据线程名去断下对应的线程。

iShot_2022-09-25_17.05.00.gif

Stream流断点

        大家常用的stream流,写时一时爽,调试火葬场,因为属于流操作,断点无法跟踪到内部,查看相应的值变化,所以当stream复杂的时候,debug起来也是一件头疼的事,但是idea中的跟踪流链功能就非常实用了,他能显示出每一步流操作的输出结果。

iShot_2022-09-25_16.32.48.gif

远程断点

        本地debug调试自然是简单,但是一旦项目部署到服务器上,没有了本地环境,该怎么调试呢?其实有办法,线上的项目可以开启远程调试,如本文开头所讲,其实debug的原理就是两个vm之间的socket通信,加上一串参数就好了,那么我们也可以给线上的服务运行的JAVA_OPTS中加上这段参数,声明好端口即可,例如:

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

        服务端的vm启动好之后,接着启动客户端的vm,当我们使用IDEA,可以创建一个remote jvm debugger的客户端,用于attach到远程jvm内部,过程如下所示,可以做到如同本地调试一样,调试远程服务了,但是需要注意的是本地的代码一定要和线上运行服务的那一套代码完全相同。

image.png

注意事项

  • 一般正式环境不会将远程debug开启,会有安全隐患。
  • 在测试环境远程debug时会将整个vm挂起,所以服务处于不可用的阶段,会影响到其他开发人员正常使用服务。
  • debug时间太长,可能会使vm宕掉。所以切记一定要及时关掉debug的客户端。