Contents

Intellij调试技巧

1. 概述

在本教程中,我们将研究一些高级 IntelliJ 调试工具

假定已经知道调试基础知识(如何开始调试、Step IntoStep Over 操作等)。如果没有,请参阅本文 以获取更多详细信息。

2. 智能步入

有时会在一行源代码中调用多个方法,例如 doJob(getArg1(), getArg2())。如果我们调用Step Into操作 (F7),调试器会按照 JVM 用于评估的顺序进入方法:  getArg1getArg2doJob

但是,我们可能希望跳过所有中间调用并直接进入目标方法。智能步入行动允许这样做。

它**默认绑定到Shift + F7**并且在调用时看起来像这样:

/uploads/intellij_debugging_tricks/1.png

现在我们可以选择目标方法继续。另外,请注意 IntelliJ 总是将最外层的方法放在列表的顶部。这意味着我们可以通过按Shift + F7 | Enter 快速转到它。

3. 丢帧

我们可能会意识到一些我们感兴趣的处理已经发生(例如当前方法参数的计算)。在这种情况下,可以删除当前的 JVM 堆栈帧以重新处理它们。

考虑以下情况:

/uploads/intellij_debugging_tricks/3.png

假设我们对调试getArg1处理感兴趣,所以我们删除当前帧(doJob方法):

/uploads/intellij_debugging_tricks/5.png

现在我们在前面的方法中

/uploads/intellij_debugging_tricks/7.png

但是,此时已经计算了调用参数,因此,我们还需要删除当前帧

/uploads/intellij_debugging_tricks/9.png

现在我们可以通过调用Step Into重新运行处理。

4. 字段断点

有时非私有字段会被其他类修改,不是通过 setter 而是直接修改(在我们不控制源代码的第三方库中就是这种情况)。

在这种情况下,可能很难理解修改何时完成。IntelliJ 允许创建字段级断点来跟踪它。

它们像往常一样设置 - 左键单击字段线上的左侧编辑器排水沟。之后,可以打开断点属性(右键单击断点标记)并配置我们是否对该字段的读取、写入或两者感兴趣

/uploads/intellij_debugging_tricks/11.png

5. 记录断点

有时我们知道应用程序中存在竞争条件,但不知道它到底在哪里。确定它可能是一个挑战,尤其是在使用新代码时。

我们可以将调试语句添加到程序的源代码中。但是,第三方库没有这种能力。

IDE 可以在这里提供帮助——它允许设置断点,一旦命中就不会阻止执行,而是生成日志语句

考虑以下示例:

public static void main(String[] args) {
    ThreadLocalRandom random = ThreadLocalRandom.current();
    int count = 0;
    for (int i = 0; i < 5; i++) {
        if (isInterested(random.nextInt(10))) {
            count++;
        }
    }
    System.out.printf("Found %d interested values%n", count);
}
private static boolean isInterested(int i) {
    return i % 2 == 0;
}

假设我们有兴趣记录实际 isInterested 调用的参数。

让我们在目标方法中创建一个非阻塞断点(Shift + 左键单击左侧编辑器排水沟)。之后,让我们打开它的属性(右键单击断点)并将目标表达式定义为 log

/uploads/intellij_debugging_tricks/13.png

运行应用程序时(请注意,仍然需要使用 Debug 模式),我们将看到输出:

isInterested(1)
isInterested(4)
isInterested(3)
isInterested(1)
isInterested(6)
Found 2 interested values

6. 条件断点

我们可能会遇到这样一种情况,即同时从多个线程调用特定方法,并且我们需要仅针对特定参数调试处理。

IntelliJ 允许创建仅在满足用户定义的条件时才暂停执行的断点

这是一个使用上述源代码的示例:

/uploads/intellij_debugging_tricks/15.png

现在调试器只有在给定参数大于 3 时才会在断点处停止。

7. 对象标记

这是最强大和最不为人知的 IntelliJ 功能。本质上很简单——我们可以将自定义标签附加到 JVM 对象上。

让我们看一下我们将用于演示它们的应用程序:

public class Test {
    public static void main(String[] args) {
        Collection<Task> tasks = Arrays.asList(new Task(), new Task());
        tasks.forEach(task -> new Thread(task).start());
    }
    private static void mayBeAdd(Collection<Integer> holder) {
        int i = ThreadLocalRandom.current().nextInt(10);
        if (i % 3 == 0) {
            holder.add(i);
        }
    }
    private static class Task implements Runnable {
        private final Collection<Integer> holder = new ArrayList<>();
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                mayBeAdd(holder);
            }
        }
    }
}

7.1.创建标记

当应用程序在断点处停止并且可以从堆栈帧到达目标时,可以标记对象。

选择它,按F11标记对象操作)并定义目标名称:

/uploads/intellij_debugging_tricks/17.png

7.2. 查看标记

现在我们甚至可以在应用程序的其他部分看到我们的自定义对象标签:

/uploads/intellij_debugging_tricks/19.png

很酷的是,即使此时从堆栈帧中无法访问标记的对象,我们仍然可以看到它的状态——打开一个评估表达式对话框或添加一个新的手表并开始输入标记的名称。

IntelliJ 提供使用*_DebugLabel*后缀来完成它:

/uploads/intellij_debugging_tricks/21.png

当我们评估它时,会显示目标对象的状态:

/uploads/intellij_debugging_tricks/23.png

7.3. 标记为条件

也可以在断点条件中使用标记:

/uploads/intellij_debugging_tricks/25.png