2年前 (2023-05-22)  相关技术 |   1 条评论  2083 
文章评分 0 次,平均分 0.0

开启异步堆栈跟踪:Settings -> Build, execution, deployment -> Debugger -> Async stack traces -> Instrumenting agent

如何在IntelliJ IDEA调试异步代码

调试异步代码是一个挑战,因为任务通常在一个线程中调度,在另一个线程执行。每个线程都有自己的堆栈,因此很难弄清楚线程启动之前发生了什么。

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()方法中的断点处停止时,有两个堆栈:

  • 当前线程(工作线程)
  • 主线程(计划任务的)

如何在IntelliJ IDEA调试异步代码

使用异步注解

异步堆栈跟踪可以使用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是参数的从零开始的数字。示例:doRunparam_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

关于

发表评论

表情 格式
  1. 非常感谢你分享这篇文章,我从中学到了很多新的知识。

    22攻略 评论达人 LV.1 2年前 (2023-05-23) [2] [1]

登录

忘记密码 ?

切换登录

注册