【Java基础】【Java Debug】IDEA Java Debug功能总结

1. IDEA Java Debug演示代码

package com.debug;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

public class main {

    private static int field;  // 在当前行设置断点,则为属性断点

    static final List list = Collections.synchronizedList(new ArrayList());

    public static void main(String[] args) throws InterruptedException {

        // -----------------------------1. 基本功能介绍及演示------------------------------
        // 1.1 step over 和 force step over 功能及使用场景
//        stepOver();

        // 1.2 step into,force step into,smart step into 功能及使用场景
        stepInto();

        // 1.3 step out 和 step out of code block的功能和使用场景
//        stepOut();

        // 1.4 run to cursor 和 force run to cursor的功能和使用场景
//        runToCursor();

        // 1.5 show execution point: 一键返回断点暂停的位置(适用于调试过程中浏览了很多其他文件,希望接着继续调试)

        // 1.6 evaluate expression
//        evaluateExpression();

        // 1.7 force return: 提前从当前方法中返回,不再执行后续的代码,手动指定返回值
//        forceReturn();

        // 1.8 throw exception: 可以用来模拟方法执行异常的情况,比如 在调试过程中想要看下代码异常分支的处理逻辑
//        throwException();

        // 1.9 reset frame: 将当前方法的执行状态回退到方法入口处,相当于"重新执行"当前方法而不影响调用链上其他方法的状态(适用场景:调试复杂代码时,希望重新观察执行流程而不终止整个调试会话)
//        resetFrame();

        // -----------------------------2. 断点类型---------------------------------------

        // 2.1 行断点(condition/stream/multi thread/force return)
//        lineBreakPoint();
        // 2.2 属性断点field (access/modification)
//        fieldBreakPoint();
        // 2.3 方法断点:主要用于接口方法的调试
//        methodBreakpoint();
        // 2.4 异常断点:以空指针异常为例,程序以debug方式运行过程中,如果代码中发生了任何空指针异常,代码都会暂停在发生异常的地方,即便异常异常被捕获处理了
//        exceptionBreakpoint();
    }


    // 1.1 step over 和 force step over的功能和使用场景
    private static void stepOver() {
        /*
        step over: 执行当前行代码,并将指示器移动到下一行,如果当前行是方法调用并且方法中包含断点,则会进入方法内断点
        force step over: 执行当前行代码,并将指示器移动到下一行,如果当前行是方法调用并且方法中包含断点,则会忽略方法内断点
         */
        System.out.println("Step over start");
        printHello();  // 在该方法内设置断点
        System.out.println("Step over end");

    }

    // 1.2 step into,force step into,smart step into的功能和使用场景
    private static void stepInto() {

        // Step Into: 进入除了JDK方法和匿名函数以外的方法
        printHello();

        // Force Step Into:可以进入所有方法,适用于需要查看Lambda和JDK源码
        System.out.println("step Into会进入除了JDK方法和匿名函数以外的方法");

        // Smart Step Into:可选择特定方法, tab键切换选中的方法,适用于一行中有多个方法的情况(链式调用+多方法嵌套)
        String result = getString().trim().concat(getNextString().toUpperCase());
        System.out.println(result);
    }


    // 1.3 step out 和 step out of code block 功能及使用场景
    private static void stepOut() {
        // step out: 执行完当前方法中剩余的代码,并返回到该方法调用的地方
        printHello();
        System.out.println("step out start!");

        // step out of code block: 执行完代码块中的代码,然后返回到代码块的下一行,经常用于直接跳过循环体,继续调试后续代码
        for (int i = 0; i < 5; i++) {
            System.out.println(i);  // 在此处设置断点,在i==2时执行step out of code block,可直接跳出该循环
        }

        System.out.println("step out end!");
    }

    // 1.4 run to cursor 和 force run to cursor 功能及使用场景
    private static void runToCursor() {
        // run to cursor: 尊重所有已设置的断点,执行期间遇到其他断点会暂停
        // force run to cursor: 忽略中间的所有断点(会临时禁用其他断点)
        System.out.println("runToCursor start!");
        printHello();  // 该方法中设置断点
        System.out.println("runToCursor end!");  // 光标移动到当前行
    }

    // 1.6 evaluate expression
    private static void evaluateExpression() {
        System.out.println("evaluateExpression start!");
        float price = 3.8f;
        int weight = 10;
        float amount = price * weight;  // 在此处设置断点,可修改上下文变量值,比如设置price=4.0f,从而改变代码的行为
        System.out.println(amount);
    }

    // 1.7 force return
    private static void forceReturn() {
        System.out.println("forceReturn start!");
        int val = returnZero();
        System.out.println("val: " + val);
        System.out.println("forceReturn end!");
    }

    // 1.8 throw exception
    private static void throwException() {
        System.out.println("throwException start!");
        try {
            printHello();
        } catch (Exception e) {
            System.out.println("err!");
        }
    }

    // 1.10 reset frame
    public static void resetFrame() {
        int result = calculateSum(5, 3);  // 在该函数内部进行reset frame操作时,程序会返回当前行,等待重新执行
        System.out.println("Result: " + result);
    }

    private static int calculateSum(int a, int b) {
        System.out.println("开始计算: a=" + a + ", b=" + b);  // 断点设在这里
        int sum = a + b;
        System.out.println("中间结果: " + sum);  // 在此处执行reset frame,会返回到当前方法调用处,此时可以手动设置a,b的值,观测该函数在不同入参情况下的表现
        sum = sum * 2;
        return sum;
    }

    // 2. 断点类型
    // 2.1 行断点: (condition/stream/multi thread/force return)
    private static void lineBreakPoint() throws InterruptedException {
        // 条件断点,设置断点时附加一个条件表达式,只有当该条件为 true 时,程序才会在断点处暂停
        int k = 0;
        for (; k < 5; k++) {
            System.out.println(k);
        }
        // 临时断点,只会在程序运行过程中触发一次,触发后自动删除的断点,断点设置项中勾选“Remove once hit”
        k = 10;
        // 依赖断点,对于断点B,只有断点A执行了才会生效
        printHello();  // 在该方法中设置一个主断点A
        k = 20;  // 在当前行设置断点,并设置依赖断点A, 则只有主断点A执行了,当前断点才会生效

        // breakpoint hit message 和 stack trace: 打印断点命中日志和方法调用栈
        printHello();

        // stream流debug,程序执行到该行时,可在调试面板中看到“Trace Current Stream Chain”功能,
        List<String> lst = Stream.of(1, 2, 5, 8, 10).filter(i -> i > 5).map(String::valueOf).toList();


        // multi thread
        Thread t = new Thread(() -> addIfAbsent(18));
        t.start();
        addIfAbsent(17);;
        t.join();
        System.out.println(list);

    }

    // 2.2 值断点
    private static void fieldBreakPoint() {
        accessField();  // 监控属性访问
        System.out.println("------------");
        modifyField();  // 监控属性修改
        System.out.println("------------");
    }

    // 2.3 方法断点
    private static void methodBreakpoint() {
        Circle circle = new Circle(1);
        double area = circle.calculateArea();  // 给Shape接口的calculateArea方法设置断点,则程序执行其子类的calculateArea方法时会自动暂停
        System.out.println("area: " + area);

    }
    // 2.4 异常断点
    private static void exceptionBreakpoint() {

        String s = null;
        try {
            s.length();
        } catch (Exception e) {
            System.out.println();
        }

    }


    private static void process(int v) {
        System.out.println(v);
    }

    private static String getString() {
        return "  hello  ";
    }

    private static String getNextString() {
        return "world";
    }

    private static void printHello() {
        System.out.println("hello, ");
        System.out.println("world!");
    }

    private static int returnZero() {
        System.out.println("returnZero start!");
        System.out.println("returnZero execute!");  // 可在此行设置断点,程序运行至该行时,通过force return强制更改该函数返回值,比如让该函数返回1
        return 0;
    }

    private static void addIfAbsent(int x) {
        if (!list.contains(x)) {
            list.add(x);
        }
    }

    private static void accessField() {
        System.out.println(field);  // 属性访问,当给field设置属性断点时,并且监控条件设置为field access时,程序执行到该行会自动暂停
    }

    private static void modifyField() {
        field = 10;  // 属性修改,当给field设置属性断点时,并且监控条件设置为field modification时,程序执行到该行会自动暂停
    }

}

图形接口类: Shape.java

package com.debug;

public interface Shape {
    // 计算面积
    double calculateArea();  // 在此处设置断点,可以跟踪所有实现类的方法,不需要再到实现类中设置断点了

}

图形接口实现类1:Circle.java

package com.debug;

public class Circle implements Shape {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

图形接口实现类2:Rectangle.java

package com.debug;

public class Rectangle implements Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double calculateArea() {
        return length * width;
    }
}

思维导图(使用mindmaster以txt方式导入):

IDEA Debug总结
	基础篇
		调试面板介绍
			step over相关
				step over
					功能/场景:执行当前行的代码,如果当前行包含方法调用,不会进入方法内部,而是直接执行完整个方法,停在下一行代码
				force step over
					功能:当遇到断点时,普通Step Over会停止,而Force Step Over会忽略这些断点继续执行
场景:想跳过当前行代码执行,但当前行方法内部有若干断点导致普通step over无法顺利跳过时
			step into相关
				step into
					功能/场景:进入除了JDK方法和匿名函数以外的方法
				force step into
					功能/场景:可以进入所有方法,适用于需要查看Lambda和JDK源码
			step out相关
				step out
					功能:执行完当前方法中剩余的代码,并返回到该方法调用的地方
场景:不再关系当前方法的剩余部分,希望快速返回到上级方法
				step out of code block
					功能:执行完代码块中的代码,然后返回到代码块的下一行
场景:直接跳过循环体调试
			run to cursor相关
				run to cursor
					功能:继续执行直到到达光标所在行
场景:想快速跳过一段已知运行正常的代码(不会忽略路径上的断点)
				force run to cursor
					功能:继续执行直到到达光标所在行
场景:想快速跳过一段已知运行正常的代码(忽略路径上的所有断点)
			show execution point
				功能:将光标跳转到当前执行点所在的代码位置
场景:在调试过程中浏览多个文件后想快速回到调试暂停的位置
			reset frame
				功能/场景:"Reset Frame"(重置帧/回退帧)允许开发者将程序的执行状态回退到当前方法的调用点,重新执行该方法
			variables&watches
				evaluate expression
					功能:在调试上下文中计算表达式
场景:需要观测某个变量或表达式的结果
				watches
					功能:添加要持续观察的变量或表达式
场景:需要持续跟踪某些复杂表达式的值
			改变方法输出
				force return
					功能:在当前方法执行到任意位置时,强制让方法立即返回,跳过剩余代码的执行
场景:① 当前方法主要逻辑已经执行完毕,想跳过后续代码执行 ② 模拟方法特定返回值,测试方法在不同返回值下程序的行为表现
				throw exception
					功能:在方法执行的任意点人为抛出指定异常
场景:测试代码的异常处理逻辑,尤其是难以真实触发的异常场景
			程序控制相关
				pause program
					功能:暂停正在运行的程序
场景:程序长时间运行或死循环时终端执行
				resume program
					功能:继续执行程序直到下一个断点或程序结束
场景:想快速执行到下一个关注点
				rerun program
					功能/场景:重新启动当前调试会话中的程序
				stop
					功能/场景:结束当前调试会话
		4种断点类型
			行断点
				条件断点
					什么是条件断点?
						指在设置断点时附加一个条件表达式,只有当该条件为 true 时,程序才会在断点处暂停
					如何设置条件断点?
						1. 在代码行号左侧单击鼠标左键,设置一个普通断点(红色圆点)
						2. 右键点击断点,打开断点设置窗口,在condition条件编辑框中输入一个布尔表达式,作为断点触发条件
					使用场景
						1. 循环中只关心某些特定迭代,避免频繁进入断点导致效率低下
						2. 只有当某个变量满足特定值或状态时才需要调试
				stream流debug
					使用介绍
						在调试stream流式代码时,可以打一个行断点,调试代码执行到该行时,在底部调试面板上会新增一个“Trace Current Stream Chain”按钮,点击该按钮会进入“Stream Trace”视图,进而可以查看stream流处理各阶段的数据情况
				依赖断点
					什么是依赖断点?
						依赖断点是一种条件断点的扩展形式,它通过设置断点之间的依赖关系来控制触发顺序
					如何设置依赖断点?
						1. 在代码中设置至少两个断点,分别作为主断点和依赖断点
2. 右键点击普通断点,选择 More 或 Edit Breakpoint。
3. 在弹出的调试面板中,勾选 Disable until selected breakpoint is hit(直到选定断点被触发时才启用),从下拉菜单中选择主断点
					使用场景
						只关心某个方法在特定条件下被调用的行为
				临时断点
					什么是临时断点?
						只会在程序运行过程中触发一次,触发后自动删除的断点
					如何设置临时断点?
						1. 右键点击普通断点,选择 More 或 Edit Breakpoint。
2. 在弹出的对话框中,勾选 Remove once hit(触发一次后移除)
			方法断点
				功能
					在方法的入口或出口处暂停程序执行
				用法
					直接左键点击方法签名左侧的空白区域设置断点
				场景
					1. 需要调试接口或者抽象方法的实现,不需要知道具体实现类就能捕获所有实现
					2. 监控方法的调用频率,想了解某个方法被调用的次数和调用者
					3. 分析方法的输入和输出
			属性断点
				功能
					当字段被使用或修改时,会自动停在字段使用或修改的行
				用法
					直接在字段属性上设置断点
				场景
					监视字段属性的访问/修改
			异常断点
				功能
					程序运行过程中如果发生了特定异常,会自动暂停在发生特定异常的代码行,即便代码中已经捕获处理了这些异常
				用法
					通过断点面板添加
						1. 点击调试工具栏的 View Breakpoints (Ctrl+Shift+F8)
2. 点击 + 按钮选择 Java Exception Breakpoints
3. 输入异常类名(如 NullPointerException)
					通过异常堆栈添加
						1. 当程序抛出未捕获异常时
2. 在异常堆栈信息上右键选择 Create Breakpoint
					通过代码编辑器添加
						1. 在代码中异常类名上右键
2. 选择 Create Breakpoint for Exception
				场景
					1. 程序崩溃但不知道异常抛出的位置,希望直接定位到异常抛出的源头,而不是捕获处理的位置
					2. 使用第三方库时出现异常,但库代码没有源码或难以调试
					3. 监控特定类型的异常,比如想统计或分析特定异常的发生频率和上下文
					4. 多线程场景调试,线程池中任务抛出异常但被吞没,捕获异步任务中的异常,避免“无声失败”
					5. 检查异常传播路径,比如可以观察调佣栈查看异常是如何从底层传播到上层的
	进阶篇
		远程调试
			远程调试原理
				Java远程调试的关键在于JDWP(Java Debug Wire Protocol),这是一个定义了客户端(通常是IDE)与目标虚拟机(被调试的程序所在的JVM)之间通信规则的协议。JDWP允许调试器控制目标虚拟机的执行,比如设置断点、检查变量值、调用栈等。
			典型启动参数
				完整启动命令
					java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar your-application.jar
				启动参数介绍
					transport=dt_socket
						指定使用socket传输
					server=y
						表示JVM处于监听状态,等待调试器的连接
					suspend=y
						是否在启动时挂起,等待调试器连接。如果设置为y,JVM启动时会暂停,直到调试器连接
					address=5005
						指定远程调试监听的端口为5005
			IDEA远程调试配置
				配置路径
					“Edit Configurations...” -> “+” -> “Remote JVM Debug”
				配置参数
					Name
						调试器名称,自定义
					Host
						远程主机IP
					Port
						远程调试端口,默认5005
		多线程调试
			调试面板
				frames区域
					展示程序当前运行的所有线程,可手动点解切换线程,查看线程的调用栈
				相关按钮
					get thread dump:获取线程转储
			断点设置
				suspend
					all
						暂停所有线程执行
					thread
						仅暂停命中断点的当前线程
			并发调试流程
				1. 复现问题 → 2. 获取线程转储 → 3. 分析锁状态 → 4. 设置针对性断点

2. Java远程调试

2.1 什么是远程调试?

Java远程调试允许开发者在本地IDE中调试运行在远程服务器上的Java应用程序,是分布式开发和问题排查的重要工具

2.2 如何进行远程调试?

Java远程调试本质上是一个典型的C/S架构,远程JVM作为Server,本地IDEA作为客户端。

步骤1:以调试方式启动远程Java程序(server启动)
启动命令(推荐):

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar your-app.jar

关键参数说明:

  • transport=dt_socket:使用 Socket 通信
  • server=y:JVM 作为调试服务端(等待 IDE 连接)
  • suspend=n:不阻塞启动(设为 y 则需调试器连接后才启动应用)
  • address=5005:监听端口(生产环境建议改用内网 IP 或 SSH 隧道)

注意: 以上启动命令指的是以调试方式运行程序时必须添加的JVM参数,如果程序有额外的JVM启动参数,也要加上

步骤2:本地创建远程调试配置并启动调试

  1. RunEdit Configurations+Remote JVM Debug

  2. 填写远程 IP 和端口(如 192.168.1.100:5005)

  3. 启动调试,点击 Debug 按钮,控制台显示 Connected to the target VM 即成功。

2.3 远程调试架构原理

sequenceDiagram participant IDE as 本地IDE<br>(IntelliJ/Eclipse等) participant JDWP as JDWP协议<br>(Java Debug Wire Protocol) participant JVM as 远程JVM<br>(调试模式) participant App as 远程Java应用 IDE->>+JVM: 1. 建立Socket连接<br>(默认端口5005) JVM-->>-IDE: 2. 返回连接确认 loop 调试会话 IDE->>+JVM: 3. 发送调试命令<br>(设置断点/单步执行等) JVM->>+App: 4. 执行应用代码 App-->>-JVM: 5. 返回执行状态 JVM-->>-IDE: 6. 返回调试信息<br>(变量值/堆栈等) end IDE->>JVM: 7. 断开调试连接
posted @ 2025-04-17 20:49  berlin-fly  阅读(51)  评论(0)    收藏  举报