IDEA 2019 debug 技巧
一、debug初识
先看下IDEA 2019 里的 debug 界面
1、Debugger:debug的面板,查看各类东西
2、Console:控制台,查看日志
3、Show Execution Point:单击后跳到此次debug最后执行位置。方便你一顿操作后不知道现在执行到哪个点了。当然,点击Frames最顶那行,也能回到最后位置。
4、Step Over:下一步(遇到调用方法不进入)
5、Step Into:进里面(如果同行有多个可以进入的,会让你移动光标选择进入的方法)
6、Force Step Into:强制进入下一步,不管是什么方法,即使是jdk封装的方法,也会进入
7、Step Out:跳出方法
8、Drop Frame:这是个非常高科技的按钮,是
后悔药
。详细在后面的调试技巧里讲
9、Run to Cursor:运行直到停在光标处(前提是光标前方无断点),方便的功能,可以不打断点停住
10、Evaluate Expression:计算表达式的值,跟watch不同,这是临时的
11、Trace Current Stream Chain:
未知
12、Frames:栈的相关信息,如线程信息,调用链。
详情见后 “Frames”
13、线程的信息,打钩是当前线程,下拉可以看到其他线程信息。
详情见后 “Frames”
14、调用链的信息,指示是怎么调用到当前断点的,双击可以进入对应的代码。
详情见后 “Frames”
15、Return:重新跑一遍debug
16、Resume Program:眼睛一闭运行,直到结束或者遇到下一个断点
17、Pause Program:
未知
,没用过,不重要
18、Stop:停止debug
19、View Breakpoints:查看所有断点。
详细请看后面的 “View Breakpoints”
20、Mute Breakpoints:静音所有断点。可以这么用:不想再在之后的断点中停住,可以点击该按钮运行剩下的代码
21、Get Thread Dump:
未知
,看名字就是得到线程的Dump文件,可以分析内存情况吧
22、Settings:做一些配置。
详细见后续 “Settings”
23、Pin Tab:钉住最前。似乎没什么用,从来不会被遮挡呀。
24、Watch的面板:展示表达式的值的面板,比起Evaluate Expression,可以长久出现,不像Evaluate是临时性查看,下一次debug就没了
25、New Watch:新增watch表达式:可以不在debug过程增加的,也可以右键选择表达式后Add to Watches(这个菜单只有在debug过程才会出现)
26、Remove Watch:删除watch表达式
27、Move Watch Up:上移调序
28、Move Watch Down:下移调序
29、Duplicate Watch:复制一份
30、Show watches in variables tab:在变量的tab页中显示watch表达式。非debug的状态下点击这个图标似乎无反应,只有debug时你会发现变化
31、Layout Settings:设置layout,所谓layout就是那些小面板。可以增加想要的layout,自己搞乱了layout也可以恢复默认。
详细见后续 “Layout Settings”
详细展开各个说明
1、Frames
1)线程信息:
- 打红色钩的,是当前线程
- 非打钩的,是其他线程
2)详情信息:
双引号里的是线程名字,之后@,之后线程ID,之后in group,之后是这个线程归属的组的名字,之后是线程的状态。如:当前线程名是Thread-0,线程ID是477,在名为main的线程组里,RUNNING状态。
有些线程没有 “组” 的信息
,如不知道Finalizer是哪个组的,这可能是比较基础的线程,所以就没有。
3)线程组:
线程组是为了好维护一堆的线程,线程组下面有线程,也可以挂着其他组。
可看打钩下面的,是追踪点。蓝色底的是debug停住的地方(断点或者光标),可以发现
(
光标也可以跟断点一样停住
)
-
当前位置,在名叫Stack02的类的myMethod方法的第10行(
这个类的包名是com.wyf.test.ideadebug
) -
导致代码运行到这里的,是上一个 “跳点”,即getString方法第11行
点击getString那行,进入的不是getString方法的开始,是getString里头的 “跳点”
- 再往上跟踪,源头是main方法的第16行
可发现,其实上述就是一个debug的跟踪链。
-
越靠近上面是 “近”,越下面越是 “源头”
-
每一行是一个跳点
(
所谓的
跳点
,其实就是从一个方法跳到另外一个方法的接触点
)
(PS:跳点的词是自创。。。专业的叫什么? 入栈点? 进入一个方法叫入栈,出来方法叫弹栈)
总的来说,这个图是一个跟踪链,从main方法16行,导致了getString的调用,在getString的11行导致了myMEthod的调用,在myMethod的10行就是debug停住的地方。
4)技巧
-
快速回到最后断点的位置
:双击Frames最上面一行 -
快速回到断点的位置
:双击Frames想进入的那行
5)其他补充
有时候Frames的方法名是
<init>
,表示的是类的初始化,即把断点打在类上,停住时显示的内容
2、View Breakpoints
这个面板显示了项目的所有断点
常见操作
- +:新增。可以看到有各种各类的Breakpoints
- -:删除
- Group by Package:左起第三个按钮。这个package不知道是什么意思,好像也不是java的 “包”
- Group by file:按文件分类,有时候可以方便找到断点位置
- Group by Class:按class分类,方便找到断点位置
其他补充
-
断点种类和特性:
-
行断点(line breakpoints)
即打在代码行的
-
类断点(官方没这个说法,是我加的)
打在类上的断点。跟行断点一样,也是出现在
Java Line Breakpoints
分类中;圆形。(
类断点在debug时也是可以停住的,new的时候,静态方法调用不停!
) -
方法断点(method breakpoints)
是打在方法上的
-
Java Field Breakpoints
打在字段上行的断点。当字段的值被改变时,断点会停在改变处(通过反射得到字段并进行改变则不会停下)
-
异常断点(Java Exception Breakpoints)
发生异常的时候要停下来的断点(图示是闪电标记)
-
JS异常断点(JavaScript Exception Breakpoints)
估计就是拦截js异常的断点,应该是自动监控的
-
-
断点的形状、颜色
见图,不同种类的断点、不同状态的断点的图示是不一样的。比如圆形、菱形、眼状、闪电状、红色、黄色、问号表示有Condition。其中打在类上的断点的图示和行断点一模一样
-
疑惑
- 断点的状态这么多,既然可以删除断点,也可以不删而临时disabled,为什么还要有suspend状态。
- 类断点:打在类名那行的断点,在静态调用的时候不会停住,在new这个类时会。感觉比较有趣,在这里提下
- 方法断点,通常打了方法断点,会在启动springboot时被提示如下,建议你取消,那方法断点存在的意义是什么? (方法断点能在方法结束后即使不打断点也会停下来,算不算它存在意义?)
3、settings
-
Show Values Inline:如下效果,在代码的右边显示变量的值,确实很方便
多说一句,阿里的Java代码规范不建议使用行注释,实际测试,在debug时会挤占变量值空间,这可能是原因
-
Show Method Return Values:如下效果
-
Auto-Variables Mode:
未知其功能,自行研究
-
Unmute Breakpoints on Session Finish:就是在debug结束后,恢复不静音。、
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HhJlwh6X-1583991955079)(/Users/stonewang/Library/Application Support/typora-user-images/image-20200311133351083.png)]
4、Layout Setttings
-
分割线之前的是可选的 layout,想要就打勾。默认情况下,似乎Threads、Memory、Overhead都没有启用
-
Restore Default Layout:恢复默认,就是你不小心把某些layout关闭了,或显示尺寸调乱了,用这个恢复
-
如果你找不到你的layout:Watches,可能用Restore也恢复不了。耐心找找,文字很难描述清楚这种情况。
-
二、调试技巧
强调:如果有个方法打了断点,被直接调用,或者是通过反射被调用,都是会停下的。
虽然反射有点像空气一样,那也还是得通过jdk的一些类调用进入的,不会凭空进入
这个调试技巧也很重要:即某个字段的值是何时赋值的。本文提到了,请详细查阅。
1、技巧
-
Drop Frame
这个是非常强大的功能,是后悔药。对当前方法重来一遍
如图的例子,
-
在
t2.test1();
停住时,是不能按Drop Frame的,此时还在主干main线程。 -
点击Resume Program时,进入test2,我们一顿调试,越过了for循环,此时我们想重新看看v的值,但是v是过了花括号就看不见了。
此时我们需要后悔药,点击Drop Frame
,回到test2方法被调用的地方(即test1那里)
注意
:可能需要注意一点,这个 “后悔药”,让你再运行一次,以本代码为例,如果在for循环里进行了入库操作或向mq发消息,那再执行一次,是不是会
重复执行
呢? 应该是会的 -
在
-
新增watch表达式
Evaluate好用,但是只有一次,可以新增表达式到watch,则可永久查看。
选中表达式,然后Add to Watches 进行添加(该菜单只有在debug时才出现)
-
调试时临时改变变量值
如图,第一次打印是hello的值,截止停在第二次打印,我们在Variables的layout里右键选中
Set Value
,设置后就打印出新值了 -
远程调试
用本地代码,调试远程的程序。
远程程序需要开启权限。另外远程部署的程序的代码,要和本地的一致,不然调试时行数对不上。详细配置方法搜索
-
给断点设置条件,满足条件的时候才会停住
设置好后,断点图标有个问号
-
堆栈信息的查看方法
上面对 Frames的layout也讲了一些知识点
。这里再补充一些。-
最上面是当前断点位置,单击后进入当前运行的位置
不一定是断点的位置,比如你在断点位置继续运行了一行,点击后就回到运行所在行,不是断点所在行。下面依次是调用链,展示了如何一步步运行到当前的。
-
点击漏斗(Hide Frames from Libraries)可以过滤jar包里的方法
比如过滤掉各种框架的,当然你自己写的类如果是在jar包里,也是会隐藏掉的。
另外注意到一个细节,Frames里灰色的是jar包里的,黑色的是非jar里的
(下图选中状态那行是非jar的代码,实际上是黑的,看不出黑不黑) -
放心调试,某个方法A,
不管A是被直接调用还是通过反射调用,你打断点,都是会停住的
,自然也会显示在Frames列表里
-
-
如果一个类或者接口,有很多子类,我怎么知道接下来执行到哪个子类了呢?
如果光看代码是比较麻烦的
,但是debug时可以断点,停了之后看它真正的类是什么就可确认。或者你可以使用step in。 -
【重要技能】我怎么知道某个成员变量的值,在何时被赋值,被谁赋值?
-
可以在变量中打断点,即 Java Field Watchpoints,如果变量值被改变就会在 “赋值的地方” 停下来,赋值处,比如setter方法,该setter无需打断点,只要变量打了断点。(
但是,但是,但是
,通过反射将字段值改变的,不会停下来) -
一般如果有setter,可以在setter打断点,停住后跟踪堆栈(
不好用,PASS
,有些赋值不通过setter,例如类内部的方法访问时直接对该变量赋值。有些是通过反射将值set进的) -
对于field watchpoints,默认只有变量值被改变时才会触发,对于访问该变量不触发,如下access
-
-
【操作技巧】右键编辑器里的断点,即可快捷操作断点
-
善用方法断点
如下在类上打断点,会在进入方法的时候停住,然后点击Resume Program后并不是直接运行出去,会在出去方法前停住。(如下,无论方法有没有返回值,都会在进入和出去的那行停住,有分支的根据具体的条件如flag的值停在
return "if"
/
return "else"
) -
善用异常断点
异常断点不能直接在编辑器添加,要在View Breakpoints面板添加,可以指定拦截指定异常,或者所有异常。当发生异常的时候,会在产生异常的地方停下,有利于分析异常时的变量情况。
-
添加步骤,在View Breakpoints->+号,如图
-
异常拦截规则,跟catch一样,比如你监控的是NPE异常,但是实际抛出ArithmeticException,则不停断点;如果你监控IOException,除了IOException,它的子异常FileNotFoundException也是能停断点的;注意Any Exception,似乎会拦截很多奇怪的异常,如IDEA加载时的异常,如
-
-
停在光标处
可以免去打断点,点击 Run to Cusor,会运行到光标处(相当于在光标处打了断点)
-
step into
以往运行主干要进入某个方法的分支,都跑进入打断点,其实很麻烦,用这个step into就行了
-
step out
跳出某个方法回到上一层调用处。好像有时候 “跳不出来”。
会产生这样的错觉是因为
如下面的例子:先停在
t2.test1();
,接着Resume Program到达test2停住,这时候点击step out,并不是回到main方法里,而是test1()里,这就是为什么我们总是觉得step out好像跳不出来,总觉得我没有进入过test1怎么会回到这里,因为已经进入了很多层了,它只能跳出上一层。这时可以一层层跳,总是会跳到最初的main方法里的。(我们经常调试spring的代码,step out就回到了很多的类似代理的地方,总之觉得乱七八糟的代码)
-
【长时间的疑惑】为什么有时候会发现,我按下Resume Program,按理说应该一路通畅,但是却在花括号结束处停住了,问题我在
}
没打断点,为什么停在那边?这个我暂时也不能完全理解。只是知道可能是类似把断点打在方法声明上(方法断点),运行到结束时,即使结尾没有断点,它都会停一下。可能是类似的机制吧