Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说最好的IDEA debug长文?看完我佛了「终于解决」,希望能够帮助你!!!。
你好,我是A哥(YourBatman)。
最近写了几篇IntelliJ IDEA系列的文章,反响蛮好。我想了下,因为并非是分享什么激活成功教程方法、推荐插件、主题这种蛋炒饭式哗众取宠的文章,而是真实对工作有帮助,对提高工作效率很有用的内容。同学们对使用IDEA还是有不少痛点,或者姿势不够正确优雅,一直以来A哥坚持写些不随波逐流、有一定深度专栏文章,哪怕只是个工具IDEA而已。
上篇文章 文末做了说明,本计划IDEA系列告一段落,但有收到几条上百字的留言和私信,觉得有些读者确实很用心在看,所以决定宠粉再干几篇。对于IDEA系列,工具嘛,研究原理没有意义,而是站在使用者的角度,介绍正确姿势和最佳实践,用工具提效是唯一目的。
当然,也有私信问我我的主题咋设置的挺好看?用了哪些好用的插件?自定义的插件如何开发?之类的,我认为这种确实没必要单独分享喽,因为用谷歌百度一下就可以找到一推拉,各行大佬写的文章多了去了
本篇介绍IDEA调试debug,因为它确实很重要。会不会debug,有没有debug的意识,懂不懂 debug 的技巧,是有没有入门编程的重要标志。 关于IDEA debug调试的文章我在CSDN里早已发表过,反响还不错(看来有痛点的人不少呀):
今天我就把它“搬过来”,并做“增强”改动分享出来,希望你喜欢。
小插曲:IDEA刚发布了其2020.3.2这个小版本,启动图换成了20周年图,IntelliJ IDEA 20周岁啦,为期2天的周年庆活动对开发者免费开放,感受一下:
Debug调试对IT从业者不是个陌生概念,工作中经常会用到它,这无关乎于初级、中级、高级程序员。调试程序的方式有多种,如:输出日志、增加辅助变量、拆分函数体、断点调试等等,本文将介绍的是断点调试 -- 一种最行之有效的调试方法。准确的讲,本文讲述是使用IntelliJ IDEA断点调试。
Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常时,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。除此之外,我们也可以使用Debug模式来跟踪代码的运行流程来学习优秀的开源框架。
俗话说编码5分钟,debug2小时,从这句话就能体现出调试的重要性,毕竟它占据你“大部分”的时间。
为了真实的体现出它的重要性,我“引经据典”,找来了几个资深行业经验的大佬用引用他们的话来表述:
我把关键词都加粗划重点了,其重要性可见一斑。大佬尚且这么认为,何况是我等?所以,本文好好阅读O(∩_∩)O哈哈~
突然被这么一问,是不是脑袋懵懵的?
一个天天都在用的“东西”,若是真要你对它下个定义说给别人听,估计一时半会还解释不清。当然喽,大道至简,领会其要义能熟练使用才是硬道理。本文作为一篇“严肃”的技术文章,自然需要先把断点这个概念用文字描述出来。
断点:为了调试而故意让程序暂停的地方。它是一种附加在源代码上面的特殊标记,在debug模式下可以触发特定动作,如暂停执行、打印线程堆栈、计算表达式的值、变量跟踪等等。断点的设置和取消全人为手动管理,若不手动处理(删除)将会和项目一直存在。
如果你看过前两篇文章,一定能解释为何它会一直存在项目里。建议你前往参阅,电梯直达
可见,断点的核心要义是暂停程序,从而在暂停那时刻就可以看到上线文的变量情况、IO情况、线程情况等信息,进而更深入的了解程序,及时发现问题和追踪到错误的根源。
断点并不是孤立存在的,它也可以有参数,从而定制出不同的断点行为,让其能在不同条件下生效,这个参数就叫断点参数。
我们平时用得比较多的条件断点,它就是断点参数的最典型应用。当然除了条件断点,其它的断点类型也是可以定制化参数的。那到底有哪些断点类型可以使用和定制呢?那么接下来就步入到本文主体内容,开始进入更有意思的部分啦。
应该没人不会打断点吧,即使你是产品经理(产品经理莫名躺枪,手动狗头~)。
打断点最简单最直接的方式就是在你想设置断点的哪一行代码的最左边窗栏鼠标左键单击一下,完成后能看到一个小红点,就表示断点设置成功啦,再点击一下就取消。形如这样:
因为我的IDEA界面简洁,尽可能的去掉了“按钮”,所以平时我自己是使用到大量的快捷键来操作IDEA,打断点也是如此经常用快捷键去完成。当然喽,很多时候也用鼠标的啦,毕竟鼠标处理还是有其很大优势的。
说明:我的快捷键是Ctrl + Shift + B,仅供给你参考
管理断点包括新增、删除断点。
对于少量断点来讲,鼠标一个个的点击给它删除掉是可以的。但若打了“大量”的断点在代码里(比如看xxx源码的时候),这时让去一个个找来删除是不太现实的,毕竟你可能自己都忘了哪儿有断点。这个时候一个管理页面/窗口就显得格外的重要了,在IDEA中提供了这样的窗口,你有多种方式打开它:
在这个管理页面,你可以对断点进行增删改。
说明:我的快捷键是Ctrl + Shift + F8,仅供给你参考
额,这讲得是不是有点过于简单了点。
启动Debug模式运行的方式有多种,比如工具栏的虫子小图标按钮、程序方法入口左键点击、菜单栏、右键菜单栏等等,下面简单演示下:
据我了解,很多同学最常用的方式是点击上方工具栏右上角的虫子图标,因为我“没有”这个图标,所以“教程”中就不演示了。A哥平时99%情况下都是使用快捷键方式启动程序,因为我认为那是最迅速和便捷的(当然不一定适合你)。
此功能我的快捷键分为两大类
另外,若要区分本次是Run运行还是Debug运行,除了看右上角小虫子图标外,更好的方式看底部控制台窗口激活的是哪个。这样看的优点是:即使同一份应用启动多次,也能快速看出来哪些debug哪些run。
值得一提的是: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个,但都比较简单和好理解。同样的从上到下过一遍:
点击会弹出这个配置窗口:
每份运行期配置都是具名且唯一的,互相隔离。运行配置可修改的项非常多,大概如下:
说明:我截图的页面可能和你不一样,因为我用的是最新版的IDEA,此页面在2020.3版本做了改版
Stop xxx:不解释
View Breakpoints:打开断点管理窗口。文上已详细解释了此窗口的用法
Mute Breakpoints:这个按钮挺有意思的,作用是让所有断点变为灰色,也就是说让它们失效。它是一个批量操作,操作对象是所有断点,而不可针对于某一个。若你现在不想把所有断点删除,但又不想它们阻拦你,那么可用这个按钮实现
Get Thread Dump:拿到当前线程的dump,可以查看到当前线程的状态。如下图:
Settings:打开设置菜单。属于高级使用,每一项开启后有什么效果,放在文下解释
显示当前方法(位于栈顶)所经过的所有方法。
说明:点击右上角的小漏斗图标可以不显示类库的方法,只显示你自己写的方法,方便调试
在此区域可以查看当前断点上下文范围内的所有变量值(即使不在本类内也可以点过去查看哦),包括static静态的。
值得注意:此区域里的变量IDEA会自动调用其toString()
方法,因此若你遇到正常运行只输出一句日志,debug输出多句这种case很可能就是这个情况哦。
有的时候变量很多,而只需要重点关注某几个变量,就可以使用Watches。
除了以上这些,还有什么动态改变变量值set Value,跳转到源码处jump to source等都是非常实用的功能,这就留你自己开发和实验哈。
有同学遇到过这个情况:明明断点激活了(程序暂停了),但是那个“操作面板”并没有出来,怎么破?
话不多说,检查你的这个配置项是勾选状态即可。这个状态IDEA默认是勾选上的,一般不用操心。
最后,站在使用层面,介绍些非常实用的“奇淫巧技”给你,这些小技巧可拿来就用。
场景描述:调试时,当我走到第三步就发现了问题,这个时候并不希望走完后续流程(比如因为前面有bug后续流程会有删除数据操作等等),这个时候怎么处理?
咔嚓,Stop程序。是的,很长一段时间里我也是这么干的,确实能达到目的。直到我发现了一个更优雅的方法:Force Return,效果为:强制返回方法返回值(自己给个值)来避免后续的流程。
指定断点的激活条件,都能称作条件断点。一般情况下,在行断点下给定一个计算表达式,结果为true就激活断点这是最常用的方式。因为上面已有案例,这里省略
多线程程序的好处固然不用多说,但总所周知它调试起来是比较困难的,比如这段:
public static void main(String[] args) {
// 共放3个"令牌"
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
// 模拟多个线程去抢
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
String name = Thread.currentThread().getName();
System.out.println(name + ",准备抢令牌");
cyclicBarrier.await();
System.out.println(name + ",已抢到");
} catch (Exception e) {
}
}, "线程" + i).start();
}
}
这个时候如果你想研究await()
方法的实现,需要具备的前提条件是多个线程进入,因此需要hold住多个线程。若只是在await()这一行打个普通的行断点,那结果是这样子的:
所有线程都是Running状态,显示这是不可能的,因为总共只有3个另外,拿完了其它的都得等待才对,所以这个根本就不是真实的执行场景,也就不可能跟踪到await()方法里面去探究其实现。
为了模拟出这种场景进行调试,就对断点阻塞条件设置为这样:
再次运行程序,线程情况如下:
都知道调试面板里的Evaluate Expression可以计算表达式/变量的值,但那毕竟还得弹个窗稍显麻烦,其实还有更为方便的方式:
用鼠标操作,效率指数级提升。这个操作方式是:鼠标指针选中表达式(IDEA智能自动选中) + 鼠标左键单击。当然喽,如果你想执行自定义的不存在于代码中的表达式,那必须调起窗口来操作。
Java 8的流行,彻底让流式编程走进我们的视野。使用Stream编程的好处众多,但一直被大家诟病的是难以阅读和难以调试,特别是后者。
为了调试它,我们经常需要插入其它断点,并分析流中的每个转换,不可为不麻烦。还好IDEA提供了处理该痛点的“能力”:当调试器在Stream API调用链之前或之内停止时,点击Trace Current Stream Chain这个图标即可以“非常好看”的图形化方式展示出来,一目了然:
需求场景:你写了一个全局异常组件,现在想测试它生效情况如何,那么时候你就需要主动抛出这种异常,一般的做法是形如这样:
// 自己在程序内主动throw一个
throw new NullPointerException();
// 或者构建个表达式
int i = 1/0;
这种做法均有一定的代码侵入性,用后还得删除。其实IDEA还提供了一种更为优雅的解决方案:
掌握了IDEA断点调试的基本技能,下面进入到本文深水区:断点类型。难度不高,依旧是使用层面的事,但由于很多同学并不知道,因此是你用于超车的好材料。
对于打断点,估计大部分同学都只会左边鼠标单击这种最基础的方式。所以,看到这个小标题估计你得再懵一次吧。what?断点还有种类?
若你也是只在代码左边鼠标单击打上“小红点”,然后嘎嘎就是干,空中转体720度向后翻腾三周半......一把唆的选手,那么接下来就坐稳喽,准备发车。
这么个姿势也许能帮你定位50%以上的问题,但还有另外一半的case呢?如for循环调试,Stream流调试,lambda调试、异常调试等这些场景,用那“一把唆”的方式就很难搞定甚至说搞不定了。断点是帮我们快速定位问题的,不同的场景打上合适的断点将能事半功倍。
殊不知,IDEA给我们开发者提供了非常的断点类型,以应对不同场景下的调试。在对应的场景下使用合适正确的断点类型,能够大大提高调试的效率,从而别人加班你下班,效率就是时间,而时间就是生命。
如图,IDEA把断点分为四大类型(截图中只有三类):
下面就到了“啃硬骨”的时候了,来吧。
使用得最最最广泛的断点类型,平时大部分情况下都使用此种断点。
从“教程”中可以看到该断点有很多的设置项,也就是有很多的断点参数可以配置,来了解下。
因为这是第一个介绍断点参数的类型,因此会说得详细些,这样子后面相同功能的参数就不用再赘述了。对照这个截图页:
All(默认):阻塞该程序内所有线程
Thread:只阻塞当前断点所在线程
如上图,不勾选Suspend:线程14和线程15正常运行,“畅通无阻”
如上图,勾选Suspend-All:在断点处,所有线程都被阻塞了,统一给我等待。
如上图,勾选Suspend-Thread:method1的线程被阻塞,但是并不影响另外一个线程调用method2。
试想一下,既然“勾选Suspend-Thread”影响更小,那为何IDEA默认帮你选择All而不是Thread呢?原因是这样子的:调试的目的就是让程序“慢下来”,最好是静止下来方便分析问题。否则,其它线程如果仍旧继续保持执行的话,可能一会这个请求改掉这个数据一会改掉那个数据,增加了不可控性。不确定的增加从而大大增加调试难度和定位问题的难度,所以索性上个“同步锁”来得省心,因此默认选中Suspend-All是合理为之。
说明:很多时候我们需要用本机连接测试环境打断点进行远程调试,若在这个case下强烈建议你使用Thread模式,否则你懂的
还有窗口里最右边的这块条件:
见名之意,一系列过滤器:过滤实例、过滤类、过滤调用者等等,一般这些们几乎不会使用(至少我目前是还没用过的),所以就一笔带过。
行断点一般配合单步调试一起使用,在看框架源码、定位基础问题等使用得特别多,是最需要掌握的一种断点类型,没得商量。
它也属于行断点的一种,只是参数不一样而已。由于它比较特殊,所以单摘出来说道说道。创建普通行断点,然后把Remove once hit复选框勾选上即是一个临时行断点,效果如下:
这种断点类型,实际使用场景其实很少。
此类断点是打在属性上的,成员属性和静态属性均可。它不是小红点,而是个红色“小眼睛”。
如图,此种断点类型特有个watch参数,两个可选值的含义为:
当想知道xxx属性的赋值是谁时,由于程序太庞杂没法知道断点打哪儿从哪开始跟踪,这个时候使用属性类型的断点一下子就搞定了,非常的方便。
断点必须打在方法签名的那一行,颜色形状是个红色的小菱形。
Watch有三个可选值:
若entry和exit都勾选,那在进入之后和出去之前都会激活断点
对于此种断点类型,可能你会说没啥卵用。毕竟自己在方法头尾打个行断点就能达到同样效果,没必要单独搞个类型嘛。
其实,它的杀手锏级使用场景是把此种类型断点打在接口方法上,这样子不管哪个实现类方法被调用,都会激活断点,是不是特别给力。
比较小众,但并不代表不重要。在我理解它比较小众,可能大多数同学不知道如何打一个异常断点,因为它不是鼠标单击就能轻松搞定。
上面介绍了异常断点它是一种全局断点类型,因此并不能在代码处直接单击,而是只能在管理窗口里统一添加:
和其它断点类型相比,至少有如下不一样:
Notification有两个可选值:
默认情况下这两个都会被勾选上,也就是说任何情况下发生此异常,都会激活断点。
知晓了异常断点的作用和触发条件,使用场景就有啦。比如当你的程序抛出了一个异常,但是一时半会你并不知道是哪行代码引起的,这个时候通过增加异常断点的方式可以实现迅速的问题定位。
每种断点类型都有自己对应的图标,且有不同的状态。我从官网趴了一张对比图,总结得特别好,在这里一并分享给你:
现在大都是微服务架构方式,每个微服务一般会有N多个上/下游依赖,如此以至于给调试带来了很大困难,毕竟你几乎不可能在本地同时把依赖都启起来用IDEA做调试。所以,远程调试来了,它是调试分布式系统的一个利器。
远程调试:顾名思义,使用本地IDEA调试远程代码(一般为QA环境,线上环境不可能开启调试端口的)。那么如何开启远程调试呢?
开启远程调试只需要两步即可:
第一步:让远程部署的那个应用支持远程调试,也就是暴露远程调试端口。方式方法为在应用启动时加上对应的JVM参数即可,JDK版本不同参数也不一样
第二步:用IDEA创建一个remote运行配置,填上远程主机的ip + 暴露的调试端口即可。操作路径为:Edit Configurations -> Add New Configuration ->
万事俱备,点击debug运行,控制台里能看到如下字样就证明你链接成功了:
值得注意的是:远程调试时请确保你本地代码和远程代码一模一样,以达到最佳效果。
若你是个Spring Boot应用,那么在jar -jar时加上JVM参数即可,那么如果是要使用传统的tomcat方式部署呢?这个时候找到传统tomcat的启动脚本startup.sh
:
#!/bin/sh
os400=false
...
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
...
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
为了加上咱们的JVM参数,只需要在exec xxx
之前添加一个变量值即可(以JDK8为例):
JPDA_OPTS='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=具体的端口号’
注意:这个key名称必须是JPDA_OPTS。
有好奇心的你可能不禁就要问了:为何加个JPDA_OPTS参数就行了呢?也没见exec xxx
使用它呀,其实不然,下面简单解释下,不展开。
exec
执行时引用了变量 $EXECUTABLE,它代表的是就是catalina.sh
这个文件,该文件里面有大量变量判断脚本,当然包括负责对JPDA_OPTS解释:
#!/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更多知识点,可自行用谷歌百度一下学习学习
这不就是Spring Boot应用形式麽?所以,如何开启,不用再废话了吧~
人和动物的最大区别之一是人会使用工具,且善于使用工具。工具被创造出来,使命就是提效的,毕竟我们不可能用记事本去写Java程序吧。
IntelliJ IDEA作为最为流行的JVM平台IDE,我们应该尽可能的去挖掘出它的效用,既然作为集成开发环境,其实很多功能都可以一站式搞定,在一个平台里做很多数据都能打通。比如IDEA的rest接口调试、数据库映射、Shell终端等等,应付平时的开发一般搓搓有余,推荐使用,毕竟软件启得越多电脑越卡不是。
用IDEA和会用IDEA是两个层次,除了代码本身,最常用的开发工具也是值得花番心思的。大道至简,知易行难,知行合一,得到功成!
本文所属专栏:IDEA,后台回复专栏名即可获取全部内容,已被*www.yourbatman.cn*收录。
看完了不一定懂,看懂了不一定会。来,文末3个思考题帮你复盘:
System.out.println("点个赞吧!");
print_r('关注【BAT的乌托邦】!');
var_dump('私聊A哥:fsx1056342982');
console.log("点个赞吧!");
NSLog(@"关注【BAT的乌托邦】!");
print("私聊A哥:fsx1056342982");
echo("点个赞吧!");
cout << "关注【BAT的乌托邦】!" << endl;
printf("私聊A哥:fsx1056342982");
Console.WriteLine("点个赞吧!");
fmt.Println("关注【BAT的乌托邦】!");
Response.Write("私聊A哥:fsx1056342982");
alert("点个赞吧!");
A哥(YourBatman)
:Spring Framework开源贡献者,Java架构师,领域专家。文章不标题党,不哗众取宠,每篇文章都成系列去系统的攻破一个知识点,每个系列可能是全网最佳/唯一。注重基本功修养,底层基础决定上层建筑。现有IDEA系列、Spring N多系列、Bean Validation系列、日期时间系列......关注免费获取