开启异步堆栈跟踪:Settings -> Build, execution, deployment -> Debugger -> Async stack traces -> Instrumenting agent
调试异步代码是一个挑战,因为任务通常在一个线程中调度,在另一个线程执行。每个线程都有自己的堆栈,因此很难弄清楚线程启动之前发生了什么。
IntelliJ IDEA通过在不同线程中的帧之间建立连接,使其变得更容易。这使您可以从工作线程回顾任务的调度位置,并调试程序,就好像执行都在同一个线程中一样。
要尝试异步堆栈跟踪,请调试以下示例:
import java.util.*;
import java.util.concurrent.*;
public class AsyncExample {
static List<Task> tasks = new ArrayList<>();
static ExecutorService executor = Executors.newScheduledThreadPool(4);
public static void main(String[] args) {
createTasks();
executeTasks();
}
private static void createTasks() {
for (int i = 0; i < 20; i++) {
tasks.add(new Task(i));
}
}
private static void executeTasks() {
for (Task task : tasks) {
executor.submit(task);
}
}
static class Task extends Thread {
int num;
public void run() {
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
printNum();
}
private void printNum() {
// Set a breakpoint at the following line
System.out.print(num + " ");
}
public Task(int num) {
this.num = num;
}
}
}
当我们在printNum()
方法中的断点处停止时,有两个堆栈:
- 当前线程(工作线程)
- 主线程(计划任务的)
使用异步注解
异步堆栈跟踪可以使用Swing和Java并发API开箱即用,但也可以手动扩展以使用您自己的自定义类。这是使用特殊注解完成的。
为了使用异步注解,您需要在项目中包含注解库。如果不能添加依赖项,则可以创建自己的注解。
注解用于定义捕获点和插入点:
- 捕获点是一种捕获堆栈跟踪的方法。在捕获点,堆栈跟踪被存储并分配一个key。捕获点用
@Async.Schedule
注解进行标记。 - 插入点是一种将先前存储的堆栈跟踪之一附加到当前堆栈的方法。堆栈按键匹配。插入点用
@Async.Execute
注解进行标记。 - 键是一个参数或对象引用,用作捕获堆栈跟踪的唯一标识符。
定义捕获点和插入点
可以对方法或其参数进行注解:
如果希望将对象引用(this
)用作键,请对方法本身进行注解,例如:
@Async.Schedule
private static void schedule(Integer i) {
System.out.println("Scheduling " + i);
queue.put(i);
}
如果希望将参数值用作键,请对方法参数进行注解,例如:
private static void schedule(@Async.Schedule Integer i) {
System.out.println("Scheduling " + i);
queue.put(i);
}
为了测试注解是如何工作的,让我们使用以下示例:
import org.jetbrains.annotations.Async;
import java.util.concurrent.*;
public class AsyncSchedulerExample {
private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
while (true) {
process(queue.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
schedule(1);
schedule(2);
schedule(3);
}
private static void schedule(@Async.Schedule Integer i) throws InterruptedException {
System.out.println("Scheduling " + i);
queue.put(i);
}
private static void process(@Async.Execute Integer i) {
// Set a breakpoint at the following line
System.out.println("Processing " + i);
}
}
定义自定义注解
如果您不想添加JetBrains Maven存储库作为项目的依赖项,可以定义自己的注解并使用它们,而不是默认的注解。
1. 为捕获点和插入点创建自己的注解(可以使用Async.java
(https://github.com/JetBrains/java-annotations/blob/master/common/src/main/java/org/jetbrains/annotations/Async.java)进行参考)。
2. 按Ctrl+Alt+S打开IDE设置,然后选择 Build -> Execution -> Deployment -> Debugger -> Async Stack Traces。
3. 单击配置注解。
4. 在异步注解配置对话框中,单击添加将自定义注解添加到异步计划注解和异步执行注解。
高级配置
基于注解的方法依赖于instrumenting agent,并且在大多数情况下都有效。有一种方法可以将所有工作单独委托给调试器。如果您:
- 需要捕获局部变量
- 无法使用批注
- 无法使用instrumenting agent
在后台,这种方法使用不可见的断点而不是注解。当到达这样的不可见断点时,将计算指定的表达式,然后使用其结果来匹配异步堆栈跟踪的部分。
灵活性和性能之间存在权衡。对于性能至关重要的高度并发项目,不建议使用此选项。
1. 按Ctrl+Alt+S打开IDE设置,然后选择 Build -> Execution -> Deployment -> Debugger -> Async Stack Traces。
2. 单击“+”并提供以下信息:
- 捕获类名:应该捕获堆栈跟踪的类的完全限定名,例如
javax.swing.SwingUtilities
- 捕获方法名称:不带参数列表和括号的方法名称,例如
invokeLater
- 捕获键表达式:其结果将用作键的表达式。在表达式中,可以使用在框架上下文中可访问的所有内容。方法参数可以指定为
param_N
,其中N是参数的从零开始的数字。示例:doRun
或param_0
- 插入类名:应该匹配堆栈跟踪的类的完全限定名,例如
java.awt.event.InvocationEvent
- 插入方法名称:不带参数列表和括号的方法名称,例如
dispatch
- 插入键表达式:其结果将用作键的表达式。在表达式中,可以使用在框架上下文中可访问的所有内容。方法参数可以指定为
param_N
,其中N是参数的从零开始的数字。示例:runnable
计算复杂的表达式和方法调用可能会影响性能。
3. (可选)如果您还想捕获本地变量(基本类型和字符串值以及调用堆栈),请选择捕获本地变量选项。请注意,这可能会减慢调试过程。
查看远程JVM中的异步堆栈跟踪
如果您正在调试远程进程,例如在Docker容器中管理的进程,您仍然可以使用JVM Instrumenting Agent来显示异步堆栈跟踪,就像它是从IDE启动的一样。
要远程使用代理,请执行以下操作:
- 将<IDEA安装文件夹>/lib/rt/debugger-agent.jar复制到远程计算机上的任何位置
- 将
-javaagent:<path to debugger agent.jar>
添加到远程JVM选项中
原文链接:https://www.jetbrains.com/help/idea/debug-asynchronous-code.html
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2842.html
非常感谢你分享这篇文章,我从中学到了很多新的知识。