1. Overview
1.概述
In this tutorial, we’ll look into some advanced IntelliJ debugging facilities.
在本教程中,我们将研究一些高级的IntelliJ调试设施。
It’s assumed that debugging basics are already known (how to start debugging, Step Into, Step Over actions etc). If not, please refer to this article for more details on that.
假设已经知道了调试的基本知识(如何开始调试、Step Into、Step Over动作等)。如果没有,请参考这篇文章以了解更多相关细节。
2. Smart Step Into
2.聪明的步入
There are situations when multiple methods are called on a single line of source code, such as doJob(getArg1(), getArg2()). If we call Step Into action (F7), the debugger goes into the methods in the order used by the JVM for evaluation: getArg1 – getArg2 – doJob.
有些情况下,在一行源代码上会调用多个方法,例如doJob(getArg1(), getArg2())。如果我们调用Step Into动作(F7),调试器会按照JVM用于评估的顺序进入这些方法。getArg1 – getArg2 – doJob。
However, we might want to skip all intermediate invocations and proceed to the target method directly. Smart Step Into action allows doing that.
然而,我们可能想跳过所有的中间调用,直接进入目标方法。Smart Step Into动作允许这样做。
It’s bound to the Shift + F7 by default and looks like this when invoked:
它默认与Shift + F7绑定,调用时看起来像这样。
Now we can choose the target method to proceed. Also, note that IntelliJ always puts the outermost method to the top of the list. That means that we can quickly go to it by pressing Shift + F7 | Enter.
现在我们可以选择目标方法来进行。另外,请注意,IntelliJ总是将最外层的方法放在列表的顶部。这意味着我们可以通过按Shift + F7 | Enter来快速进入它。
3. Drop Frame
3.跌落框架
We may realize that some processing we’re interested in has already happened (e.g. current method argument’s calculation). In this case, it’s possible to drop the current JVM stack frame(s) in order to re-process them.
我们可能会意识到,我们感兴趣的一些处理已经发生了(例如,当前方法参数的计算)。在这种情况下,我们可以放弃当前的JVM堆栈帧,以便重新处理它们。
Consider the following situation:
考虑以下情况。
Suppose we’re interested in debugging getArg1 processing, so we drop the current frame (doJob method):
假设我们对调试getArg1处理感兴趣,所以我们放弃当前帧(doJob方法)。
Now we’re in the previous method:
现在我们在前一种方法中。
However, the call arguments are already calculated at this point, so, we need to drop the current frame as well:
然而,此时的调用参数已经被计算出来了,所以,我们也需要放弃当前帧。
Now we can re-run the processing by calling Step Into.
现在我们可以通过调用Step Into来重新运行处理。
4. Field Breakpoints
4.现场中断点
Sometimes non-private fields are modified by other classes, not through setters but directly (that is the case with third-party libraries where we don’t control the source code).
有时,非私有字段会被其他类修改,不是通过设置器,而是直接修改(这就是第三方库的情况,我们无法控制源代码)。
In such situations, it might be hard to understand when the modification is done. IntelliJ allows creating field-level breakpoints to track that.
在这种情况下,可能很难理解什么时候完成了修改。IntelliJ允许创建字段级断点来跟踪。
They are set as usual – left-click on the left editor gutter on the field line. After that, it’s possible to open breakpoint properties (right-click on the breakpoint mark) and configure if we’re interested in the field’s reads, writes, or both:
它们的设置与往常一样–左键点击字段行上的左侧编辑器沟槽。之后,可以打开断点属性(右击断点标记),配置我们是否对字段的读、写或两者都感兴趣。
5. Logging Breakpoints
5.记录中断点
Sometimes we know that there is a race condition in the application but don’t know where exactly it is. It may be a challenge to nail it down, especially while working with new code.
有时我们知道应用程序中存在一个竞赛条件,但不知道它到底在哪里。这可能是一个挑战,尤其是在处理新的代码时,要把它钉死。
We can add debugging statements to our program’s sources. However, there’s no such ability for third-party libraries.
我们可以在我们的程序源中添加调试语句。然而,对于第三方库来说,没有这样的能力。
The IDE can help here – it allows setting breakpoints that don’t block execution once hit, but produce logging statements instead.
集成开发环境在这里可以提供帮助–它允许设置断点,一旦击中就不会阻止执行,而是产生记录语句。
Consider the following example:
请考虑以下例子。
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;
}
Suppose we’re interested in logging actual isInterested call’s parameters.
假设我们对记录实际isInterested调用的参数感兴趣。
Let’s create a non-blocking breakpoint in the target method (Shift + left-click on the left editor gutter). After that let’s open its properties (right-click on the breakpoint) and define the target expression to log:
让我们在目标方法中创建一个非阻塞的断点(Shift+左键点击左边编辑器的沟槽)。之后,让我们打开它的属性(右击断点),定义目标表达式来记录。
When running the application (note that it’s still necessary to use Debug mode), we’ll see the output:
当运行该应用程序时(注意仍然需要使用Debug模式),我们会看到输出。
isInterested(1)
isInterested(4)
isInterested(3)
isInterested(1)
isInterested(6)
Found 2 interested values
6. Conditional Breakpoints
6.条件性断点
We may have a situation where a particular method is called from multiple threads simultaneously and we need to debug the processing just for a particular argument.
我们可能会遇到这样的情况:一个特定的方法被多个线程同时调用,而我们需要调试的只是某个特定参数的处理。
IntelliJ allows creating breakpoints that pause the execution only if a user-defined condition is satisfied.
IntelliJ允许创建断点,仅在满足用户定义的条件时暂停执行。
Here’s an example that uses the source code above:
下面是一个使用上述源代码的例子。
Now the debugger will stop on the breakpoint only if the given argument is greater than 3.
现在,只有当给定的参数大于3的时候,调试器才会在断点上停止。
7. Object Marks
7.物体标记
This is the most powerful and the least known IntelliJ feature. It’s quite simple in the essence – we can attach custom labels to JVM objects.
这是最强大也是最不为人知的IntelliJ功能。它的本质很简单–我们可以给JVM对象附加自定义标签。
Let’s have a look at an application that we’ll use for demonstrating them:
让我们看一下我们将用来演示的一个应用程序。
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. Creating Marks
7.1.创建标记
An object can be marked when an application is stopped on a breakpoint and the target is reachable from stack frames.
当应用程序在断点上停止,并且目标可以从堆栈帧中到达时,一个对象可以被标记。
Select it, press F11 (Mark Object action) and define target name:
选择它,按F11(标记对象动作)并定义目标名称。
7.2. View Marks
7.2.查看标记
Now we can see our custom object labels even in other parts of the application:
现在,我们甚至可以在应用程序的其他部分看到我们的自定义对象标签。
The cool thing is that even if a marked object is not reachable from stack frames at the moment, we can still see its state – open an Evaluate Expression dialog or add a new watch and start typing the mark’s name.
最酷的是,即使一个被标记的对象目前无法从堆栈帧中到达,我们仍然可以看到它的状态–打开一个Evaluate Expression对话框或者添加一个新的watch,开始输入标记的名字。
IntelliJ offers to complete it with the _DebugLabel suffix:
IntelliJ提供了用_DebugLabel后缀来完成它。
When we evaluate it, the target object’s state is shown:
当我们评估它时,目标对象的状态被显示出来。
7.3. Marks as Conditions
7.3.标记为条件
It’s also possible to use marks in breakpoint conditions:
也可以在断点条件中使用标记。
8. Conclusion
8.结论
We checked a number of techniques that increase productivity a lot while debugging a multi-threaded application.
我们检查了一些技术,在调试多线程应用程序时,这些技术可以大大增加生产力。
This is usually a challenging task, and we cannot understate the importance of tooling’s help here.
这通常是一项具有挑战性的任务,我们不能低估工具的帮助在这里的重要性。