Idea 调试 Debug 技巧
-
IDEA
为我们提供了很多简单且非常强大的调试功能 -
在需要调试的行打上断点,
debugger
方式启动Idea
控制台如下
调试功能说明
Show Execution Point
- 快捷键:Alt + F10
- 回到当前激活的断点处:当你的鼠标不在断点所处的行,点击之后,会立马复位到断点处;
Step Over
- 快捷键:F8
- 单步调试:逐行往下执行,如果执行行有其他方法,不会进入对应的方法;日常
Debug
用的最多的一个功能。
Step Into
- 快捷键:F7
- 进入方法体内部。该功能会进入自定义的方法或者三方库的方法,不会进入
JDK
的方法
Force Step Into
- 快捷键:Alt + Shift + F7
- 强制进入方法体内部,与
Step Into
不同的是,会进入JDK
的方法;
Step Out
- 快捷键:Shift + F8
- 跳出方法体;一般会配合
(Force)Step Into
一起使用
Drop frame
- 回到方法的调用处,同时上下文内所有的变量的值也回到那个时候。
- 该按钮能够点击的前提条件是:当前所处的方法有上级方法,如果你是
main
方法里,那么按钮就是灰色,无法点击;
Run to Cursor
- 快捷键:Alt + F9
- 将代码运行到光标处,光标停在哪里就运行到哪里;
Evaluate Expression
- 快捷键:Alt + F8
- 表达式计算器;可执行任意合法的表达式。
Trace Current Stream Chain
- 追踪当前
Stream
流;只有在Stream
代码上,此按钮才会亮起。
Rerun Main
- 快捷键:Ctrl + F5
- 查询执行
Debug
;
Resume Program
- 快捷键:F9
- 恢复程序;当因为断点导致代码停止之后,此功能可以让持续恢复执行;有下一个断点时,会跳转到下一个断点;没有下一个断点,会执行到持续结束;
Stop Main
- 快捷键:Ctrl + F12
- 停止程序;
View Breakpoints
- 快捷键:Ctrl + Shift + F8
- 打开断点管理窗口;
Mute Breakpoints
- 停用所有的断点;
Get Thread Dump
- 拿到当前线程的
Dump
,可以查看当前线程的状态;
筛选
调试小技巧
行断点
- 行断点的图标是一个 圆形的红点,在需要断点的代码行头点击,即可添加断点
方法断点
- 将断点打在某个具体的方法上,方法执行的时候,会进入断点;
- 举个方法调试最常用的
Debug
场景:当阅读源码或者自己写业务需求的时候,经常会用到策略、模板方法等设计模式;在调试的时候,需要知道,当前接口方法或者抽象方法的执行,到底是走的哪一个具体的实现,用方法调试就能很方便的找到;如下示例;
- 接口
Service
有两个具体的实现:ServiceA
和ServiceB
,分别实现了接口的method
方法,调试的过程中就可以将断点打在接口的method方法上;当我们在Main方法中实例化了ServiceB
,断点就自动进入到ServiceB
的method()
方法了; - 接口
Service
public interface Service {
void method();
}
ServiceA
public class ServiceA implements Service{
@Override
public void method() {
System.out.println("Service A");
}
}
ServiceB
public class ServiceB implements Service{
@Override
public void method() {
System.out.println("Service B");
}
}
Main
public class Main {
public static void main(String[] args) {
Service serviceB = new ServiceB();
serviceB.method();
}
}
更多功能点
Condition
:用于输入表达式,进行过滤Watch
:Method entry
和Method exit
至少有一个选项存在。Emulated
:用于提高调试性能Method entry
:进入方法时激活断点Method exit
:退出方法时激活断点
属性断点
- 在属性的行头点击即可添加一个小眼睛一样的属性监听断点,用于监听某个属性的读写变化过程;
更多功能点
Condition
用于输入表达式,进行过滤Watch
Filed Access
:读取此属性时(写入时不管)Filed modification
:写入此属性时(读取时不管)
异常断点
- 异常断点是开发、调试的时候经常用到的一个功能,用于快速定位到那行代码出现了异常;
设置方式: - 第一步,使用快捷键
Ctrl + Shift + F8
打开配置窗口; - 第二步,点击左上角的
+
号; - 第三步,选择
Java Exception Breakpoints
; - 第四步,添加需要断点的异常,如:
ArithmeticException
; - 第五步,
Debug
运行,当出现指定的异常时,就会进入断点;
更多功能点
Condition
:用于输入表达式,进行过滤Watch
Caught excetion
:只有当你自己try-catch
了这个异常才会激活断点Uncaught excetion
:只有当你自己不try-catch
时才会激活断点
临时断点
- 临时断点是指只触发一次的断点,之后就自动取消了;一般用于特定的场合下需要确认值是符合我们的预期,完了之后就不在需要了;
- 设置及演示过程如下:
设置方式: - 第一步,设置断点
- 第二步,使用快捷键
Ctrl + Shift + F8
打开配置窗口; - 第三步,找到对应的断点;
- 第四步,勾选
Remove once hit
; - 第五步,
Debug
运行,当断点触发一次之后,就自动取消了;
断点条件
- 设置断点的触发条件,也是阅读源码、修复
Bug
经常用到的一个功能,比如读Spring
源码,研究Bean
生命周期的时候,就可以根据Bean
的name
去设置断点条件,用来判断之后在操作指定对象的时候,才进入断点; - 如下示例:
1、for
循环之后只有i
是2
的倍数时,才进入断点,可以在Conditon
中填入i % 2 == 0
;
2、0-10000的
循环,当i
等于5000
的时候,进入断点,其他的时候忽略,可以在Conditon
中填入i == 5000
设置过程: - 第一步,设置断点
- 第二步,右键断点处,打开操作界面
- 第三步,输入表达式,比如循环时只有偶数才断点,可以输入
i % 2 == 0
模拟异常
开发过程中,有时候需要人为制造一些异常,比如事务操作(@Transactional
),需要验证是否能达到回滚的效果;
比如下面的伪代码:
// 伪代码 假如这里是个事务操作
// @Transactional
public void save() throws RuntimeException{
table1Save();
// 我先在这里测试一下,异常之后,是否会滚
//throw new RuntimeException("异常了");
table2Save();
table3Save();
}
void table1Save(){}
void table2Save(){}
void table3Save(){}
当咱希望在执行table2Save()
的时候,抛个异常,让整个操作回滚,通常的做法是会在代码中人为抛一个异常:
throw new RuntimeException("异常了");
这样做并没有什么错,但是不是很优雅,而且还存在一下的两个问题:
- 只能在方法的末尾抛异常;流程中间抛,后面的代码会报错
- 有风险这种业务功能中人为抛异常,如果一不小心忘记删除,将这个异常提交上去,就是人为的生产事故,可能带来比较严重的后果;
IDEA优雅模拟异常
那有没有什么更好的方式呢?IDEA
给我们提供了更加优化的模拟异常方案,不用写异常代码,可以利用工具直接抛出异常,操作步骤如下:
操作步骤:
- 第一步,在要模拟异常的地方加上断点;
- 第二步,
Debug
模式运行代码并进入断点; - 第三步,
Frames
中找到对应的断点记录; - 第四步,右键,选择
Throw Execption
; - 第五步,输入你想抛出的异常,点击
ok
,即可抛出对应的异常;
多线程调试
- 多线程开发的时候,线程的调度策略并不由代码控制,导致断点调试的过程可能会在线程间跳来跳去,如果逻辑复杂点,跳着跳着可能就蒙蔽了;如下示例:
public class Main {
public static void main(String[] args) {
System.out.println("0 main start");
new Thread(() -> {
System.out.println("1 hello");
}, "thread1").start();
new Thread(() -> {
System.out.println("2 world");
}, "thread2").start();
System.out.println("3 main end");
}
}
- 如果把断点打在
System.out.println
上,除了0
是能保证第一个输出的,1、2、3
的执行顺序是没办法保证的; - 默认情况下,断点的
suspend
设置是all
,顺序并不固定;
- 如果将所有断点的
suspend
设置为Thread
之后,就会按着线程的顺序,逐个去执行:
修改变量
- 在断点的过程中去修改某个变量的值;常见的场景是:当某个变量,因为逻辑
bug
导致属性值和预期的不一样,但是又不想从头再debug
一遍,就可以直接在调试的过程中修改成预期的值,并继续执行后续的步骤;
断点执行代码/方法/表达式
断点过程中,可以执行一段表达式、代码或者方法
- 代码
- 方法执行
- 表达式
更多功能
- 上面列举了绝大部分常用的
Debug
功能,但是这并不是所有的,一些不常用功能可以根据需要选择使用
远程调试
- 非常实用且特别装B的一个技能,当线上代码出现
Bug
之后,可以通过此方式用本地代码进行远程调试,快速定位问题并修复; - 注意:远程调试必须保证本地代码和线上代码版本一致,否则不会进入断点;
设置步骤
- 添加一个用于远程调试的接口
@RestController public class RemoteDebugController { @GetMapping("debug") public Integer debug(Integer p){ System.out.println(p); return p; } }
- 将代码打成
jar
包mvn clean package -Dmaven.test.skip=true
IDEA
设置远程调试启动项
以下是几个重要参数的说明Name
:名称,不重要,随意填写;Host
:远端服务所处的机器IP
,这里是本机测试,所以填写127.0.0.1
即可,实际使用填写远端服务所处的真实IP
PORT
:远端调试的端口- 远端服务运行时的
JVM
参数
IDEA
工具帮我们生成的服务运行时需要添加的JVM
参数,直接复制使用即可;
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5555
- 启动项目
为了演示,这里就不在IDEA中启动了,直接在CMD窗口下启动测试项目,记得用上上面生成的参数
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5555 -jar spring-boot-001-hello-world-0.0.1-SNAPSHOT.jar
- 启动过程出现
socket
的监听日志,说明正常
Listening for transport dt_socket at address: 5555
IDEA
远程调试配置
- 测试:左侧为
jar
包运行的控制台;右上方为IDEA
的界面;右下方为浏览器,模拟客户端请求;
如下图示:
- 当客户端发起请求的时候,
IDEA
就会进入断点,当执行通过,可以看出,左侧控制台就会打印出对应的日志; - 线上调试,务必要给断点加上条件,比如特定测试账号才进去断点;避免让真是用户的请求也进入断点,影响用户的使用;
- 通过此方式,如果远端的代码有
bug
,就可以直接在本地的IDEA
工具中进行调试,非常的方便;