你好,我是A哥(YourBatman)。

最近写了几篇IntelliJ IDEA系列的文章,反响蛮好。我想了下,因为并非是分享什么破解方法、推荐插件、主题这种蛋炒饭式哗众取宠的文章,而是真实对工作有帮助,对提高工作效率很有用的内容。同学们对使用IDEA还是有不少痛点,或者姿势不够正确优雅,一直以来A哥坚持写些不随波逐流、有一定深度专栏文章,哪怕只是个工具IDEA而已。

上篇文章 文末做了说明,本计划IDEA系列告一段落,但有收到几条上百字的留言和私信,觉得有些读者确实很用心在看,所以决定宠粉再干几篇。对于IDEA系列,工具嘛,研究原理没有意义,而是站在使用者的角度,介绍正确姿势和最佳实践,用工具提效是唯一目的。

 

当然,也有私信问我我的主题咋设置的挺好看?用了哪些好用的插件?自定义的插件如何开发?之类的,我认为这种确实没必要单独分享喽,因为用谷歌百度一下就可以找到一推拉,各行大佬写的文章多了去了

本篇介绍IDEA调试debug,因为它确实很重要。会不会debug,有没有debug的意识,懂不懂 debug 的技巧,是有没有入门编程的重要标志。 关于IDEA debug调试的文章我在CSDN里早已发表过,反响还不错(看来有痛点的人不少呀):

今天我就把它“搬过来”,并做“增强”改动分享出来,希望你喜欢。

本文提纲

版本约定

  • IntelliJ IDEA:2020.3.2

小插曲:IDEA刚发布了其2020.3.2这个小版本,启动图换成了20周年图,IntelliJ IDEA 20周岁啦,为期2天的周年庆活动对开发者免费开放,感受一下:

正文

Debug调试对IT从业者不是个陌生概念,工作中经常会用到它,这无关乎于初级、中级、高级程序员。调试程序的方式有多种,如:输出日志、增加辅助变量、拆分函数体、断点调试等等,本文将介绍的是断点调试 -- 一种最行之有效的调试方法。准确的讲,本文讲述是使用IntelliJ IDEA断点调试。

Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常时,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。除此之外,我们也可以使用Debug模式来跟踪代码的运行流程来学习优秀的开源框架。

断点调试有多重要?

俗话说编码5分钟,debug2小时,从这句话就能体现出调试的重要性,毕竟它占据你“大部分”的时间。

为了真实的体现出它的重要性,我“引经据典”,找来了几个资深行业经验的大佬用引用他们的话来表述:

  1. 调试技巧比编码技巧 更为重要,因为花费在调试上的时间往往比编码还多,学到的东西比编码中学到的更丰富
  2. 调试技能重要性甚⾄ 超过学习⼀门语⾔
  3. 不会调试的程序员, 肯定编制不出任何好的软件

我把关键词都加粗划重点了,其重要性可见一斑。大佬尚且这么认为,何况是我等?所以,本文好好阅读O(∩_∩)O哈哈~

什么是断点?

突然被这么一问,是不是脑袋懵懵的?

一个天天都在用的“东西”,若是真要你对它下个定义说给别人听,估计一时半会还解释不清。当然喽,大道至简,领会其要义能熟练使用才是硬道理。本文作为一篇“严肃”的技术文章,自然需要先把断点这个概念用文字描述出来。

断点:为了调试而故意让程序暂停的地方。它是一种附加在源代码上面的特殊标记,在debug模式下可以触发特定动作,如暂停执行、打印线程堆栈、计算表达式的值、变量跟踪等等。断点的设置和取消全人为手动管理,若不手动处理(删除)将会和项目一直存在。

 

如果你看过前两篇文章,一定能解释为何它会一直存在项目里。建议你前往参阅,电梯直达

可见,断点的核心要义是暂停程序,从而在暂停那时刻就可以看到上线文的变量情况、IO情况、线程情况等信息,进而更深入的了解程序,及时发现问题和追踪到错误的根源。

断点参数

断点并不是孤立存在的,它也可以有参数,从而定制出不同的断点行为,让其能在不同条件下生效,这个参数就叫断点参数

我们平时用得比较多的条件断点,它就是断点参数的最典型应用。当然除了条件断点,其它的断点类型也是可以定制化参数的。那到底有哪些断点类型可以使用和定制呢?那么接下来就步入到本文主体内容,开始进入更有意思的部分啦。

断点的基本使用

应该没人不会打断点吧,即使你是产品经理(产品经理莫名躺枪,手动狗头~)。

打断点最简单最直接的方式就是在你想设置断点的哪一行代码的最左边窗栏鼠标左键单击一下,完成后能看到一个小红点,就表示断点设置成功啦,再点击一下就取消。形如这样:

因为我的IDEA界面简洁,尽可能的去掉了“按钮”,所以平时我自己是使用到大量的快捷键来操作IDEA,打断点也是如此经常用快捷键去完成。当然喽,很多时候也用鼠标的啦,毕竟鼠标处理还是有其很大优势的。

 

说明:我的快捷键是Ctrl + Shift + B,仅供给你参考

管理断点

管理断点包括新增、删除断点。

对于少量断点来讲,鼠标一个个的点击给它删除掉是可以的。但若打了“大量”的断点在代码里(比如看xxx源码的时候),这时让去一个个找来删除是不太现实的,毕竟你可能自己都忘了哪儿有断点。这个时候一个管理页面/窗口就显得格外的重要了,在IDEA中提供了这样的窗口,你有多种方式打开它:

  1. 菜单栏方式:Run -> view breakpoints,缺点是路径太长太麻烦
  2. Actions方式:双击shift调出Actions窗口,输入view breakpoints即可打开
  3. 任意断点处鼠标右键:选择more即可打开管理窗口。缺点是:你至少得找到一个断点作为抓手(当然喽你可以任意处随意打一个点进去也成)
  4. 调试窗口:该打开方式下面会提到
  5. 快捷键方式:毫无疑问,这是我最为推荐的方式喽

在这个管理页面,你可以对断点进行增删改

 

说明:我的快捷键是Ctrl + Shift + F8,仅供给你参考

如何debug模式运行?

额,这讲得是不是有点过于简单了点。

启动Debug模式运行的方式有多种,比如工具栏的虫子小图标按钮、程序方法入口左键点击、菜单栏、右键菜单栏等等,下面简单演示下:

Gif大了来,查阅地址:https://img-blog.csdnimg.cn/20210128073454666.gif

据我了解,很多同学最常用的方式是点击上方工具栏右上角的虫子图标,因为我“没有”这个图标,所以“教程”中就不演示了。A哥平时99%情况下都是使用快捷键方式启动程序,因为我认为那是最迅速和便捷的(当然不一定适合你)。

此功能我的快捷键分为两大类

  1. 运行右上角当前选中的入口类。它有一组快捷键
    1. Ctrl + Shift + Alt + enter:Run运行
    2. Ctrl + Shift + Alt + \:Debug运行
  2. 因为很多时候需要从 新的入口启动程序,做Spring Boot工程开发可能体会不到(入口只有一个),但在做教程、Demo的时候程序入口是经常变化的,所以不可能每次都还人肉去改启动类,效率太低。为此我就新设置了这组快捷键
    1. Ctrl + Shift + Alt + [:Run运行, 鼠标焦点所在作为入口
    2. Ctrl + Shift + Alt + ]:Debug运行, 鼠标焦点所在入口

另外,若要区分本次是Run运行还是Debug运行,除了看右上角小虫子图标外,更好的方式看底部控制台窗口激活的是哪个。这样看的优点是:即使同一份应用启动多次,也能快速看出来哪些debug哪些run。

Debug模式运行

值得一提的是:debug模式运行,若没有任何断点被激活(比如你压根就没打断点),效果和run模式启动是一样(但控制台窗口不一样,因此日志输出的位置也就不一样)。

调试窗口详解

调试窗口是我们断点调试的操作面板,熟练的使用此面板推提高效率和掌握更多技巧非常重要。先来认识下它:

此操作面板上按钮不少,对Debug调试有多熟练很大程度上是由操作此面板的熟练度决定的。

调试按钮

最常用的一排按钮,入门必备。

一共9个按钮,从左往右依次解释下: 

1.Show Execution Point:回到当前激活的断点处。效果:若你鼠标现在在别的页面/别的类上面,点击它快速“归位” 

2.Step Over步过:也叫单步调试,一行一行往下走,若这一行是方法也不会进入里面去。这个应该是平时使用得最多的按钮了,没有之一。所以,建议记住你的快捷键来提高效率哈 

3.Step Into步入:进入方法体内部。这里的方法指的你自定义的方法or三方库的方法,不会进入到JDK官方的方法里(如上面的System.out.println()这种它是不会进去的) 

4.Force Step Into强制步入:能进入任何方法,包括JDK的。一般查看底层源码才会用到它 

5.Step Out步出:它是搭配(Force) Step Into一起使用的,当通过step into进入到方法体内部想出来时,一般有两种方案:单步调试慢慢出来,另一个就是step out(推荐) 

6.Drop frame:回到当前方法的调用处,同时上下文内所有的变量的值也回到那个时候。该按钮能够点击的前提条件是:当前所处的方法有上级方法,如果你是main方法里,那么按钮就是灰色喽 

7.Run to Cursor运行到光标处:你想要代码在哪里停一下,就把光标放在哪就成。这个功能实在太好用了,大大缓解了密密麻麻的断点,强烈推荐 

8.

Evaluate Expression表达式计算器:看图标就是个计算器嘛,所以你可以在这里执行任何合法的表达式



Trace Current Stream Chain跟踪当前Stream流:只有代码停在Stream流语句上,此图标才点亮可以被点击。这是IDEA提供的由于调试Stream流的杀手锏级别的功能,放在文下详细解释

这一排按钮非常重要,甚至是最重要,一定要熟练掌握,可以大大提高调试代码的效率,亲测有效。

服务按钮

把最左边一竖排定义为服务按钮,为调试过程提供服务。

一共10个,但都比较简单和好理解。同样的从上到下过一遍:

  1. Rerun xxx:关闭当前程序,重新运行
  2. Modify Run Configuration:顾名思义,修改运行的配置。点击此按钮的效果同点击右上角的框框:

点击会弹出这个配置窗口:

每份运行期配置都是具名且唯一的,互相隔离。运行配置可修改的项非常多,大概如下:

 

说明:我截图的页面可能和你不一样,因为我用的是最新版的IDEA,此页面在2020.3版本做了改版

  1. Resume Program:恢复程序。当断点激活时程序“停止”了,点击这个按钮就是恢复的意思。它给到的效果是: 跳到下一个断点(用这句话解释貌似更容易理解些),若后面没有断点就直接运行结束了。这个按钮 非常常用
  2. Pause Program:暂停程序。嗯,只要你现在“卡”在断点处,那么状态就是Pause的状态。这时候就有疑问了,难道这个按钮一直是灰色不可点状态?有啥用呢?我网络上看了看,几乎 没人能够解释它的作用,这里A哥尝试给你解释下,用张图给你整得明明白白,服服帖帖:

  1. Stop xxx:不解释

  2. View Breakpoints:打开断点管理窗口。文上已详细解释了此窗口的用法

  3. Mute Breakpoints:这个按钮挺有意思的,作用是让所有断点变为灰色,也就是说让它们失效。它是一个批量操作,操作对象是所有断点,而不可针对于某一个。若你现在不想把所有断点删除,但又不想它们阻拦你,那么可用这个按钮实现

  4. Get Thread Dump:拿到当前线程的dump,可以查看到当前线程的状态。如下图:

    子线程当前状态为Runnable
  5. Settings:打开设置菜单。属于高级使用,每一项开启后有什么效果,放在文下解释

  1. Pin tab:如果你这会调试xxx这个程序很频繁,那么把它“钉”上会更有助于效率提升

方法调用栈

显示当前方法(位于栈顶)所经过的所有方法。

说明:点击右上角的小漏斗图标可以不显示类库的方法,只显示你自己写的方法,方便调试

变量区Variables

在此区域可以查看当前断点上下文范围内的所有变量值(即使不在本类内也可以点过去查看哦),包括static静态的。

值得注意:此区域里的变量IDEA会自动调用其toString()方法,因此若你遇到正常运行只输出一句日志,debug输出多句这种case很可能就是这个情况哦。

Watches变量跟踪

有的时候变量很多,而只需要重点关注某几个变量,就可以使用Watches。

除了以上这些,还有什么动态改变变量值set Value,跳转到源码处jump to source等都是非常实用的功能,这就留你自己开发和实验哈。

为何调试窗口没自动打开?

有同学遇到过这个情况:明明断点激活了(程序暂停了),但是那个“操作面板”并没有出来,怎么破?

话不多说,检查你的这个配置项是勾选状态即可。这个状态IDEA默认是勾选上的,一般不用操心。

断点调试的奇淫巧技

最后,站在使用层面,介绍些非常实用的“奇淫巧技”给你,这些小技巧可拿来就用。

强制返回(中断debug)

场景描述:调试时,当我走到第三步就发现了问题,这个时候并不希望走完后续流程(比如因为前面有bug后续流程会有删除数据操作等等),这个时候怎么处理?

咔嚓,Stop程序。是的,很长一段时间里我也是这么干的,确实能达到目的。直到我发现了一个更优雅的方法:Force Return,效果为:强制返回方法返回值(自己给个值)来避免后续的流程。

条件断点

指定断点的激活条件,都能称作条件断点。一般情况下,在行断点下给定一个计算表达式,结果为true就激活断点这是最常用的方式。因为上面已有案例,这里省略

多线程调试

多线程程序的好处固然不用多说,但总所周知它调试起来是比较困难的,比如这段:

// 自己在程序内主动throw一个
throw new NullPointerException();

// 或者构建个表达式
int i = 1/0;

这种做法均有一定的代码侵入性,用后还得删除。其实IDEA还提供了一种更为优雅的解决方案:


掌握了IDEA断点调试的基本技能,下面进入到本文深水区:断点类型。难度不高,依旧是使用层面的事,但由于很多同学并不知道,因此是用于你超车的好材料。

四大断点类型

对于打断点,估计大部分同学都只会左边鼠标单击这种最基础的方式。所以,看到这个小标题估计你得再懵一次吧。what?断点还有种类?

若你也是只在代码左边鼠标单击打上“小红点”,然后嘎嘎就是干,空中转体720度向后翻腾三周半......一把唆的选手,那么接下来就坐稳喽,准备发车。

这么个姿势也许能帮你定位50%以上的问题,但还有另外一半的case呢?如for循环调试,Stream流调试,lambda调试、异常调试等这些场景,用那“一把唆”的方式就很难搞定甚至说搞不定了。断点是帮我们快速定位问题的,不同的场景打上合适的断点将能事半功倍。

殊不知,IDEA给我们开发者提供了非常的断点类型,以应对不同场景下的调试。在对应的场景下使用合适正确的断点类型,能够大大提高调试的效率,从而别人加班你下班,效率就是时间,而时间就是生命。

如图,IDEA把断点分为四大类型(截图中只有三类):

  1. Line breakpoint(行断点):图中红色小圆圈。顾名思义,在指定代码行设置断点
  2. Field watchpoint(属性断点):图中红色小眼睛。打在类的属性(static or 非static)上的断点,作用是在该属性 读取和写入时激活
  3. Method breakpoint(方法断点):图中红色小菱形。标记在方法签名的那一行,在该方法执行的入口/出口处被激活
  4. Exception breakpoint(异常断点):红色小闪电。这是一个特殊但很好用的断点,当程序抛出 指定异常时会激活异常断点。和其它断点不同,异常断点是项目 全局的,它不需要打在具体某一行上

下面就到了“啃硬骨”的时候了,来吧。

行断点Line breakpoint

使用得最最最广泛的断点类型,平时大部分情况下都使用此种断点。

Gif太大了放不上来,查阅地址:https://img-blog.csdnimg.cn/20210128011543377.gif

从“教程”中可以看到该断点有很多的设置项,也就是有很多的断点参数可以配置,来了解下。

断点参数

因为这是第一个介绍断点参数的类型,因此会说得详细些,这样子后面相同功能的参数就不用再赘述了。对照这个截图页:

  • Enabled:不解释。但需注意:若此项不勾选上,小红点并不会消失,而是由实心的变为 空心的,当然喽,一般情况下并不会动此项
  • Suspend:众所周知,断点激活时会阻塞程序的继续运行,从而阻塞当前线程。但是当你发现它是个复选框的时候,有没有被诧异到?并且,并且,并且你还可以根本就不勾选它,有何区别:
    • All(默认):阻塞该程序内所有线程

    • Thread:只阻塞当前断点所在线程

      如上图,不勾选Suspend:线程14和线程15正常运行,“畅通无阻”

    • Gif大了来,查阅地址:https://img-blog.csdnimg.cn/20210128062330308.gif

    • 如上图,勾选Suspend-All:在断点处,所有线程都被阻塞了,统一给我等待。

    • 若不勾选选中:此断点相关活动(如打日志等)依旧正常进行,只是 不阻塞进程
    • 若勾选中:

#!/bin/sh
os400=false

...

PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh

...

exec "$PRGDIR"/"$EXECUTABLE" start "$@"

为了加上咱们的JVM参数,只需要在exec xxx之前添加一个变量值即可(以JDK8为例):

#!/bin/sh

cygwin=false
darwin=false
...
if [ "$1" = "jpda" ] ; then
  if [ -z "$JPDA_TRANSPORT" ]; then
    JPDA_TRANSPORT="dt_socket"
  fi
  if [ -z "$JPDA_ADDRESS" ]; then
    JPDA_ADDRESS="localhost:8000"
  fi
  if [ -z "$JPDA_SUSPEND" ]; then
    JPDA_SUSPEND="n"
  fi
  if [ -z "$JPDA_OPTS" ]; then
    JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND"
  fi
  CATALINA_OPTS="$JPDA_OPTS $CATALINA_OPTS"
  shift
fi
...
 

关于JVM调试平台JPDA更多知识点,可自行用谷歌百度一下学习学习

嵌入式Tomcat如何开启远程调试?

这不就是Spring Boot应用形式麽?所以,如何开启,不用再废话了吧~

总结

人和动物的最大区别之一是人会使用工具,且善于使用工具。工具被创造出来,使命就是提效的,毕竟我们不可能用记事本去写Java程序吧。

IntelliJ IDEA作为最为流行的JVM平台IDE,我们应该尽可能的去挖掘出它的效用,既然作为集成开发环境,其实很多功能都可以一站式搞定,在一个平台里做很多数据都能打通。比如IDEA的rest接口调试、数据库映射、Shell终端等等,应付平时的开发一般搓搓有余,推荐使用,毕竟软件启得越多电脑越卡不是。

用IDEA和会用IDEA是两个层次,除了代码本身,最常用的开发工具也是值得花番心思的。大道至简,知易行难,知行合一,得到功成!