「Java」Debug断点调试常用技巧

Java
330
0
0
2023-07-22

Debug操作技巧

Show Execution Point

将光标回到当前断点停顿的地方

Step Over

执行当前行代码,并将运行进度跳转到下一行。

Step Into

进入到当前代码行的方法内部。

Step Out

从方法内部出去

Force Step Into

强制进入Java自带方法的内部

Run to Cursor

将光标定位到想到达的代码行

点击Run to Cursor

Drop Frame

丢弃当前虚拟机栈帧

初始:

进入方法:

丢弃当前帧:

也就是说,我们退回了上一步进入方法之前。

Evaluate Expression

可以用它来评估表达式

如 p.getName()等。

Force Return | 避免操作资源

我们在调试代码的时候中间出现了异常,但是我们又没有做异常捕获,稀里糊涂地把错误数据存到了数据库中,我们又需要将这些数据给删除,将数据库复原,才能达到之前我们需要的效果。

所以,接下来我们讲一讲如何避免操作资源,强制返回。

 public static void saveResource() {
    System.out.println("shit happens");
    
    System.out.println("save to db");
    System.out.println("save to redis");
    System.out.println("send message to mq for money payout");
} 

debug:

我们发现程序出现了异常

Force Return

它会只打印shit happens,不会继续向下执行了。

Trace Current Stream Chain | Stream Debug

 public static void streamDebug() {
    // stream chain
    Arrays.asList(, 2, 3, 45).stream()
            .filter(i -> i % == 0 || i % 3 == 0)
            .map(i -> i * i)
            .forEach(System.out::print);
} 

左下角平铺模式Flat Mode:

断点常用技巧

断点(Breakpoint)

设置断点: 在代码里需要调试的地方,鼠标双击代码行号的左边,再次双击即可取消断点。

在调试中可以设置的断点类型有五种:

  • 行断点:
  • spring在注册Bean定义(registerBeanDefinition)时,如果是org.springframework.demo.MyBean,就挂起线程,可以开始单步调试了。
  • 对于命中次数(hit count)的使用,一般是在循环中,第N个对象的处理有问题,设置hit count = N, 重调试时,可以方便到达需要调试的循环次数时,停下来调试。
  • 方法断点:
  • 方法断点的好处是可以从方法方法进入或者退出时停下来调试,类似行断点,而且只有行断点和方法断点有条件和访问次数的设置功能。
  • 但是方法断点还有另外一个好处,如果代码编译时,指定不携带调试信息,行断点是不起作用的,只能打方法断点。
  • 有兴趣的可以将Add line number…前的勾去掉,调试下看看。
  • 观察断点:
  • 在成员变量上打的断点。只有对象成员变量有效果,静态成员变量不起作用。
  • 可以设置变量被访问或者设置的时候挂起线程/VM。
  • 异常断点:
  • 系统发生异常时,在被捕获异常的抛出位置处或者程序未捕获的异常抛出处挂起线程/VM, 也可以指定是否包括异常的子类也被检测。
  • 类加载断点:
  • 在类名上打的断点。接口上是打不了类加载断点的,但是抽象类是可以的,只是在调试的时候,断点不会明显进入classloader中,单步进入知会进入到子类的构造方法中,非抽象类在挂起线程后单步进入就会到classloader中(如果没有filter过滤掉的话)。类加载断点不管是打在抽象或者非抽象类上,都会在类第一次加载或者第一个子类第一次被加载时,挂起线程/VM。
注意: 每种断点的设置有些许不一样,可以在断点上右键->Breakpoint properties进行设置,但一般在断点窗口有快速设置的界面,Breakpoint properties中多了filter, 其实比较鸡肋,用处不大。

调试状态

启动服务开始调试:

  • 方法一: 例如上图的代码中,鼠标点击main方法–>右键Debug As–>Java Application开始java代码调试;
  • 方法二: 直接点击“调试”按钮,即点击小瓢虫边上的倒三角,选择Debug As–>Java Application;
  • 方法三: 快捷键F11;方法四,菜单栏选择Run–>Debug,还有其他方法此处不再赘述了。
  • 开发工具首次调试会弹出提示,需要切换到Debug工作区,勾选“Remember my decision”,下次便不再提示。

调试执行:

功能

快捷键

描述

备注

Step Info

F5

单步进入(如果有方法调用,将进入调用方法中进行调试)

逐语句

Step Over

F6

单步跳过(不进入行的任何方法调用中,直接执行完当前代码行,并跳到下一行)

逐过程

Step Return

F7 单步返回(执行完当前方法,并从调用栈中弹出当前方法,返回当前方法被调用处)

跳出


Resume

F8

恢复正常执行(直到遇到下一个断点)

继续运行

Run to Line

Ctrl+R

执行到当前行(将忽略中间所有断点,执行到当前光标所在行)


Drop To Frame

回退到指定方法开始处执行,这个功能相当赞。

在方法调用栈上的某个方法右键,选择Drop To Frame就可以从该方法的开始处执行,比如 重新执行本方法,可以在本方法上用Drop To Frame,将从本方法的第一行重新执行。

当然对于有副作用的方法,比如 数据库操作,更改传入参数的对象内容等操作可能重新执行就不再是你想要的内容了。


Copy Stack

拷贝当前线程栈信息


断点

 public class BreakPointDemo {
    // 行断点
    public static void line() {
        System.out.println("this is the line break point");
    }
    
    // 详细断点(源断点)
    public static void detailLine() {
        System.out.println("this is the detail line break point");
    }
    
    // 方法断点 | 接口跳转实现类
    public static void method() {
        System.out.println("this is from method");
        IService iservice = new IServiceImpl();
        iservice.execute();
    }
    
    // 异常断点 | 全局捕获
    public static void exception() {
        Object o = null;
        o.toString();
        System.out.println("this line will never be print!");
    }
    
    // 字段断点 | 读写监控
    public static void field() {
        Person p = new Person("field",);
        p.setAge();
        System.out.println(p);
    }
    
    public static void main(String[] args) {
        line();
        detailLine();
        method();
        exception();
        field();
    }
} 

行断点

 // 行断点
public static void line() {
    System.out.println("this is the line break point");
} 

使用鼠标左键点击代码左侧:

右键点击行断点,我们也可以进行一些断点停顿的条件设置:

如 i == 20等条件。

Suspend也可以选择线程模式,我们可以切换不同的线程,来观察不同线程的该语句的运行效果。(如果是All的话,那就是哪一个线程先过来,那就是哪个线程)

详细断点

 // 详细断点(源断点)
public static void detailLine() {
    System.out.println("this is the detail line break point");
} 

SHIFT+鼠标左键:

debug:

方法断点 | 接口跳转实现类

方法断点 = 方法起始行断点 + 方法结尾行断点

 // 方法断点 | 接口跳转实现类
public static void method() {
    System.out.println("this is from method");
    IService iservice = new IServiceImpl();
    iservice.execute();
} 

在方法上打断点:

debug:

第一个断点停留在方法体内第一行代码:

第二个断点停留在方法体内返回的最后一行代码:

在接口方法上打断点:

真正运行的是接口方法的实现类:

如果我们有很多的实现类,我们具体不知道是哪一个,我们只需要在接口方法上打一个断点,它就会自动地跳到接口具体的实现类方法上。

异常断点 | 全局捕获

 // 异常断点 | 全局捕获
public static void exception() {
    Object o = null;
    o.toString();
    System.out.println("this line will never be print!");
} 

异常断点会停顿在报出异常的具体代码行。

  1. 点击View Breakpoints
  2. 在异常断点处添加新的异常断点
  3. 接下来,只要你的程序遇到空指针异常,它就会停顿到发出空指针异常的那一行代码那里。

没有显式打断点:

debug:

这个异常断点对于我们异常调试很方便。

字段断点 | 读写监控

 // 字段断点 | 读写监控
public static void field() {
    Person p = new Person("field",);
    p.setAge();
    System.out.println(p);
} 

在类的字段属性上打断点:

我们在字段左边打了一个字段断点(小眼睛),它就会去监控该字段属性的整个生命周期的值的变化。

dubug:

第一个:构造方法修改了属性值

第二个:setter方法修改了属性值