轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

      • Java8--Lambda 表达式、Stream 和时间 API
      • Java集合
      • Java IO
      • Java 文件操作
      • Java 网络编程
      • Java运行期动态能力
      • Java可插入注解处理器
      • Java基准测试(JMH)
      • Java性能分析(Profiler)
      • Java调试(JDI与JDWP)
        • 一、引言
          • 1、调试的重要性
          • 2、Java平台调试架构(JPDA)
        • 二、Java调试接口(JDI)
          • 1、JDI概述
          • 2、JDI的核心组件
          • 2.1、VirtualMachineManager(虚拟机管理器)
          • 2.2、VirtualMachine(虚拟机接口)
          • 2.3、事件系统
          • 3、JDI的应用场景
        • 三、Java调试线协议(JDWP)
          • 1、JDWP概述
          • 2、JDWP协议结构
          • 2.1、数据包格式
          • 2.2、命令集分类
          • 2.3、传输层实现
          • 3、JDWP启动参数详解
        • 四、JDI与JDWP的关系
          • 1、架构层次关系
          • 2、工作流程示例
          • 3、选择使用JDI还是JDWP
        • 五、实战示例
          • 1、使用JDI创建完整的调试器
          • 2、使用JDWP实现底层调试器
          • 3、JDWP命令扩展示例
          • 4、IDE调试实践:IntelliJ IDEA案例分析
          • 5、远程调试配置
        • 六、高级调试技巧
          • 1、条件断点
          • 2、日志断点
          • 3、异常断点
          • 4、方法断点
          • 5、字段观察点
        • 七、性能优化建议
          • 1、调试对性能的影响
        • 八、常见问题与解决方案
          • 1、无法连接到目标JVM
          • 2、断点不生效
          • 3、调试时程序卡死
        • 九、总结
          • 1、核心要点回顾
          • 2、扩展学习资源
      • Java管理与监控(JMX)
      • Java加密体系(JCA)
      • Java服务发现(SPI)
      • Java随机数生成研究
      • Java数据库连接(JDBC)
      • Java历代版本新特性
      • 写好Java Doc
      • 聊聊classpath及其资源获取
    • 并发

    • 经验

    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 核心
轩辕李
2023-05-05
目录

Java调试(JDI与JDWP)

# 一、引言

# 1、调试的重要性

在Java开发过程中,调试是定位和解决问题的关键技术。无论是处理复杂的业务逻辑错误、性能瓶颈还是并发问题,掌握调试技术都能显著提升开发效率。

有效的调试不仅能帮助我们:

  • 快速定位问题根源,减少排查时间
  • 深入理解代码执行流程和运行时状态
  • 验证代码逻辑的正确性
  • 优化程序性能和资源使用

# 2、Java平台调试架构(JPDA)

Java平台提供了完整的调试架构 JPDA(Java Platform Debugger Architecture),它包含三个核心组件:

  1. JVMTI(JVM Tool Interface):JVM层面的调试接口,提供底层调试能力
  2. JDWP(Java Debug Wire Protocol):定义调试器与JVM之间的通信协议
  3. JDI(Java Debug Interface):面向调试器开发的高级API

这三个组件协同工作,构成了Java调试的完整技术栈:

调试器(IDE) <--JDI--> 前端进程 <--JDWP--> 后端进程 <--JVMTI--> JVM

本文将深入探讨JDI和JDWP的原理与实践,帮助你理解Java调试的底层机制。

# 二、Java调试接口(JDI)

# 1、JDI概述

JDI(Java Debug Interface)是JPDA的前端接口,为调试器开发提供了纯Java的高级API。它封装了底层的JDWP协议细节,让开发者能够专注于调试逻辑的实现。

JDI的主要能力包括:

  • 虚拟机管理:启动、连接和断开目标JVM
  • 断点管理:设置行断点、方法断点、异常断点等
  • 执行控制:单步执行、继续运行、暂停线程
  • 变量检查:读取和修改变量值、查看对象内容
  • 表达式求值:在调试上下文中执行代码
  • 事件处理:监听断点命中、异常抛出等调试事件

# 2、JDI的核心组件

# 2.1、VirtualMachineManager(虚拟机管理器)

VirtualMachineManager是JDI的入口点,负责管理所有的虚拟机连接器:

VirtualMachineManager vmManager = Bootstrap.virtualMachineManager();

它提供三种类型的连接器:

  • LaunchingConnector:启动新的JVM进程并连接
  • AttachingConnector:连接到已运行的JVM进程
  • ListeningConnector:监听并等待JVM连接

# 2.2、VirtualMachine(虚拟机接口)

VirtualMachine接口代表一个被调试的JVM实例,是调试操作的核心:

// 获取所有已加载的类
List<ReferenceType> classes = vm.allClasses();

// 获取所有线程
List<ThreadReference> threads = vm.allThreads();

// 获取事件请求管理器
EventRequestManager erm = vm.eventRequestManager();

# 2.3、事件系统

JDI的事件系统基于观察者模式,包含三个核心概念:

  • EventRequest:事件请求,定义要监听的事件类型
  • EventQueue:事件队列,存储JVM产生的调试事件
  • EventSet:事件集合,批量处理同一时刻的多个事件

常见的事件类型:

  • BreakpointEvent:断点命中
  • StepEvent:单步执行完成
  • ExceptionEvent:异常抛出
  • ClassPrepareEvent:类加载完成
  • ThreadStartEvent/ThreadDeathEvent:线程生命周期

# 3、JDI的应用场景

JDI不仅用于IDE的调试功能,还有许多其他应用场景:

  1. IDE调试器:Eclipse、IntelliJ IDEA、NetBeans等IDE的调试功能
  2. 远程调试工具:支持跨网络的远程调试
  3. 性能分析器:通过JDI收集方法执行时间、调用栈等信息
  4. 测试框架:动态分析测试覆盖率
  5. 热部署工具:在运行时替换类定义
  6. 教学工具:可视化展示程序执行流程

# 三、Java调试线协议(JDWP)

# 1、JDWP概述

JDWP(Java Debug Wire Protocol)是调试器前端与目标JVM之间的二进制通信协议。它定义了一套标准的命令集和数据格式,使得不同的调试器能够与任何支持JDWP的JVM进行通信。

JDWP的关键特性:

  • 平台无关:二进制协议,不依赖特定操作系统
  • 传输无关:支持Socket、共享内存等多种传输方式
  • 双向通信:支持命令-响应和异步事件通知
  • 轻量级:最小化性能开销

# 2、JDWP协议结构

# 2.1、数据包格式

JDWP定义了三种数据包类型,每种都有固定的头部结构:

命令包(Command Packet):

+--------+--------+--------+--------+--------+--------+--------+
| Length (4字节) | ID (4字节) | Flags | CmdSet | Cmd |
+--------+--------+--------+--------+--------+--------+--------+
|                         Data (可变长度)                      |
+--------+--------+--------+--------+--------+--------+--------+

响应包(Reply Packet):

+--------+--------+--------+--------+--------+--------+
| Length (4字节) | ID (4字节) | Flags | Error Code |
+--------+--------+--------+--------+--------+--------+
|                  Data (可变长度)                    |
+--------+--------+--------+--------+--------+--------+

# 2.2、命令集分类

JDWP命令按功能分为多个命令集:

| 命令集 | ID | 功能描述 | |--------|----|| | VirtualMachine | 1 | 虚拟机级别操作(版本、能力、类列表等) | | ReferenceType | 2 | 类型信息查询(字段、方法、源文件等) | | ClassType | 3 | 类操作(调用静态方法、设置值等) | | Method | 6 | 方法信息(行号表、变量表、字节码) | | ThreadReference | 11 | 线程控制(暂停、恢复、栈帧) | | EventRequest | 15 | 事件请求管理(设置断点、监听事件) |

# 2.3、传输层实现

JDWP支持多种传输方式:

  • Socket传输(dt_socket):最常用,支持本地和远程调试
  • 共享内存(dt_shmem):仅Windows,性能更好
  • 命名管道:特定平台支持

# 3、JDWP启动参数详解

启用JDWP调试需要在JVM启动时添加参数:

# JDK 5-8 使用 -agentlib:jdwp
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 MainClass

# JDK 9+ 需要指定监听地址
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 MainClass

参数说明:

  • transport:传输方式(dt_socket、dt_shmem)
  • server:y表示JVM作为调试服务器,n表示作为客户端
  • suspend:y表示启动时暂停等待调试器连接
  • address:监听地址和端口
  • timeout:等待调试器连接的超时时间(毫秒)

# 四、JDI与JDWP的关系

# 1、架构层次关系

JDI和JDWP在JPDA架构中处于不同层次,它们的关系可以这样理解:

┌─────────────────────────────────────┐
│         调试器应用层                 │
├─────────────────────────────────────┤
│            JDI API                  │  <- 高级Java API
├─────────────────────────────────────┤
│         JDI实现层                    │  <- 将API调用转换为JDWP命令
├─────────────────────────────────────┤
│         JDWP协议层                   │  <- 二进制通信协议
├─────────────────────────────────────┤
│     传输层(Socket/SharedMem)        │
├─────────────────────────────────────┤
│         JVMTI后端                   │  <- JVM内部实现
└─────────────────────────────────────┘

# 2、工作流程示例

以设置断点为例,看看JDI和JDWP如何协作:

  1. 应用层调用JDI:
BreakpointRequest bpReq = erm.createBreakpointRequest(location);
bpReq.enable();
  1. JDI转换为JDWP命令:
命令集: EventRequest (15)
命令: Set (1) 
数据: eventKind=BREAKPOINT, location=...
  1. JDWP发送二进制数据:
[Length][ID][Flags=0][CmdSet=15][Cmd=1][Data...]
  1. JVM处理并返回响应:
[Length][ID][Flags=0x80][ErrorCode=0][RequestID]

# 3、选择使用JDI还是JDWP

特性 JDI JDWP
编程语言 Java 任意语言
API级别 高级面向对象API 底层二进制协议
易用性 简单直观 复杂,需处理协议细节
功能完整性 封装了常用功能 完全控制,可访问所有功能
适用场景 Java调试工具开发 跨语言调试器、特殊需求

# 五、实战示例

# 1、使用JDI创建完整的调试器

假设我们有以下简单的Java程序作为我们要调试的应用:

// TestApp.java
public class TestApp {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
        int result = add(1, 2);
        System.out.println("The result is: " + result);
    }

    private static int add(int a, int b) {
        return a + b;
    }
}

为了使用SimpleDebugger来调试这个应用,首先编译TestApp.java:

javac TestApp.java

然后,在另一个终端窗口中启动TestApp,并指定调试参数以便SimpleDebugger可以连接到它:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 TestApp

现在,我们需要修改SimpleDebugger的代码以便在虚拟机启动后附加到我们的TestApp:

import com.sun.jdi.*;
import com.sun.jdi.connect.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.*;
import java.io.IOException;

public class SimpleDebugger {
    public static void main(String[] args) throws Exception {
        // 1. 获取虚拟机管理器
        VirtualMachineManager vmManager = Bootstrap.virtualMachineManager();
        
        // 2. 查找Socket连接器
        AttachingConnector connector = findConnector(vmManager, "com.sun.jdi.SocketAttach");
        
        // 3. 设置连接参数
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("hostname").setValue("localhost");
        arguments.get("port").setValue("8000");
        
        // 4. 连接到目标JVM
        VirtualMachine vm = connector.attach(arguments);

        // 5. 等待TestApp类加载完成
        EventRequestManager erm = vm.eventRequestManager();
        ClassPrepareRequest classPrepareRequest = erm.createClassPrepareRequest();
        classPrepareRequest.addClassFilter("TestApp");
        classPrepareRequest.enable();
        
        // 恢复VM执行
        vm.resume();
        
        // 6. 在类加载后设置断点
        ReferenceType refType = null;
        EventQueue eventQueue = vm.eventQueue();

        // 7. 事件循环处理
        boolean vmExited = false;
        while (!vmExited) {
            EventSet eventSet = eventQueue.remove();
            for (Event event : eventSet) {
                if (event instanceof ClassPrepareEvent) {
                    // 类加载完成,设置断点
                    ClassPrepareEvent classPrepareEvent = (ClassPrepareEvent) event;
                    refType = classPrepareEvent.referenceType();
                    
                    // 在add方法设置断点(第6行)
                    try {
                        List<Location> locations = refType.locationsOfLine(6);
                        if (!locations.isEmpty()) {
                            BreakpointRequest bpReq = erm.createBreakpointRequest(locations.get(0));
                            bpReq.enable();
                            System.out.println("断点设置成功: " + locations.get(0));
                        }
                    } catch (AbsentInformationException e) {
                        System.err.println("无法获取行号信息");
                    }
                } else if (event instanceof BreakpointEvent) {
                    // 处理断点事件
                    BreakpointEvent bpEvent = (BreakpointEvent) event;
                    ThreadReference thread = bpEvent.thread();
                    StackFrame frame = thread.frame(0);
                    
                    System.out.println("\n断点命中:");
                    System.out.println("  位置: " + bpEvent.location());
                    System.out.println("  线程: " + thread.name());
                    
                    // 打印局部变量
                    try {
                        List<LocalVariable> variables = frame.visibleVariables();
                        System.out.println("  局部变量:");
                        for (LocalVariable var : variables) {
                            Value value = frame.getValue(var);
                            System.out.println("    " + var.name() + " = " + value);
                        }
                    } catch (AbsentInformationException e) {
                        System.out.println("  无法获取变量信息");
                    }
                } else if (event instanceof VMDisconnectEvent) {
                    vmExited = true;
                    System.out.println("\n调试会话结束");
                }
            }
            eventSet.resume();
        }
    }

    private static AttachingConnector findConnector(VirtualMachineManager vmManager, String name) {
        for (AttachingConnector connector : vmManager.attachingConnectors()) {
            if (connector.name().contains(name)) {
                return connector;
            }
        }
        throw new IllegalStateException("无法找到连接器: " + name);
    }

}

接下来,编译和运行调试器:

# JDK 8及以下版本
javac -cp "$JAVA_HOME/lib/tools.jar" SimpleDebugger.java
java -cp "$JAVA_HOME/lib/tools.jar:." SimpleDebugger

# JDK 9及以上版本(使用模块系统)
javac --add-modules jdk.jdi SimpleDebugger.java
java --add-modules jdk.jdi SimpleDebugger

成功运行后,你将看到类似输出:

断点设置成功: TestApp.add(TestApp.java:6)

断点命中:
  位置: TestApp.add(TestApp.java:6)
  线程: main
  局部变量:
    a = 1
    b = 2

调试会话结束

# 2、使用JDWP实现底层调试器

以下是一个使用JDWP实现的简单调试器的完整示例代码。

为了演示方便,我们使用了硬编码的端口号8000。在实际使用中,你需要将其替换为实际调试目标虚拟机监听的端口号。

import java.io.*;
import java.net.Socket;

public class SimpleJDWPDebugger {

    public static void main(String[] args) throws Exception {
        // 1. 连接到Java虚拟机
        Socket socket = new Socket("localhost", 8000);
        DataInputStream in = new DataInputStream(socket.getInputStream());
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());

        // 2. 发送JDWP命令并接收响应
        byte[] commandPacket = createCommandPacket();
        out.write(commandPacket);

        byte[] header = new byte[11];
        in.readFully(header);
        int replyPacketLength = readInt(header, 0) - 11;
        byte[] replyPacket = new byte[replyPacketLength];
        in.readFully(replyPacket);

        // 3. 处理响应
        handleReplyPacket(replyPacket);

        // 4. 关闭连接
        in.close();
        out.close();
        socket.close();
    }

    private static byte[] createCommandPacket() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);

        try {
            dos.writeInt(11); // 数据包长度
            dos.writeInt(1); // 数据包ID
            dos.writeByte(0); // flags: 0 for command
            dos.writeShort(1); // 命令集ID(VirtualMachine)
            dos.writeShort(3); // 命令ID(AllClasses)
        } catch (IOException e) {
            e.printStackTrace();
        }

        return baos.toByteArray();
    }

    private static void handleReplyPacket(byte[] replyPacket) throws IOException {
        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(replyPacket));

        int numberOfClasses = dis.readInt();
        System.out.println("已加载的类数量: " + numberOfClasses);

        for (int i = 0; i < numberOfClasses; i++) {
            byte refTypeTag = dis.readByte();
            long classId = dis.readLong();
            String signature = readString(dis);
            int status = dis.readInt();

            System.out.println("Class ID: " + classId + ", Signature: " + signature);
        }
    }

    private static int readInt(byte[] bytes, int offset) {
        return ((bytes[offset] & 0xFF) << 24) | ((bytes[offset + 1] & 0xFF) << 16) | ((bytes[offset + 2] & 0xFF) << 8) | (bytes[offset + 3] & 0xFF);
    }

    private static String readString(DataInputStream dis) throws IOException {
        int length = dis.readInt();
        byte[] bytes = new byte[length];
        dis.readFully(bytes);
        return new String(bytes, "UTF-8");
    }
}

这个示例展示了如何直接使用JDWP协议与JVM通信。使用步骤:

  1. 启动目标程序(启用JDWP):
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000 TestApp
  1. 运行JDWP调试器:
javac SimpleJDWPDebugger.java
java SimpleJDWPDebugger

输出示例:

已加载的类数量: 428
Class ID: 1, Signature: Ljava/lang/Object;
Class ID: 2, Signature: Ljava/lang/String;
...
Class ID: 427, Signature: LTestApp;

# 3、JDWP命令扩展示例

下面展示如何实现更多JDWP命令:

// 获取JVM版本信息
private static byte[] createVersionCommand() {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    try {
        dos.writeInt(11);     // 包长度
        dos.writeInt(1);      // 包ID
        dos.writeByte(0);     // flags: 命令
        dos.writeByte(1);     // 命令集: VirtualMachine
        dos.writeByte(1);     // 命令: Version
    } catch (IOException e) {
        e.printStackTrace();
    }
    return baos.toByteArray();
}

// 获取所有线程
private static byte[] createAllThreadsCommand() {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    try {
        dos.writeInt(11);     // 包长度
        dos.writeInt(2);      // 包ID
        dos.writeByte(0);     // flags: 命令
        dos.writeByte(1);     // 命令集: VirtualMachine
        dos.writeByte(4);     // 命令: AllThreads
    } catch (IOException e) {
        e.printStackTrace();
    }
    return baos.toByteArray();
}

注意:直接使用JDWP协议编程较为复杂,需要处理二进制数据和协议细节。在实际项目中,建议使用JDI这样的高级API。

# 4、IDE调试实践:IntelliJ IDEA案例分析

当在IntelliJ IDEA中启动调试时,IDE会自动配置JDWP参数。观察控制台输出可以了解其工作原理:

# Windows环境下的调试命令
C:\Program Files\Java\jdk-17\bin\java.exe \
  -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:59516,suspend=y,server=n \
  -javaagent:C:\Users\AppData\Local\JetBrains\IntelliJIdea2022.3\captureAgent\debugger-agent.jar=file:/C:/Users/AppData/Local/Temp/capture.props ...

命令参数解析:

  1. JDWP配置:

    • -agentlib:jdwp=transport=dt_socket:使用Socket传输
    • address=127.0.0.1:59516:监听本地端口59516
    • suspend=y:启动时暂停,等待调试器连接
    • server=n:JVM作为客户端连接到IDEA调试服务器
  2. IDEA调试增强:

    • -javaagent:debugger-agent.jar:IDEA的调试代理,提供额外功能:
      • 热重载(HotSwap):修改代码后无需重启
      • 表达式求值:在断点处执行任意代码
      • 条件断点:基于表达式的断点触发
      • 异步栈追踪:跟踪异步代码执行

# 5、远程调试配置

在生产环境调试时,需要配置远程调试:

1. 服务器端配置:

# 开放调试端口(生产环境慎用)
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 \
     -jar application.jar

2. IDEA远程调试配置:

Run -> Edit Configurations -> + -> Remote JVM Debug
- Host: 服务器IP
- Port: 5005
- Command line arguments for remote JVM: 自动生成

3. 安全建议:

  • 使用SSH隧道转发调试端口
  • 限制调试端口的访问IP
  • 调试完成后立即关闭调试端口

# 六、高级调试技巧

# 1、条件断点

在复杂循环中定位问题:

for (int i = 0; i < 1000; i++) {
    processItem(items.get(i));  // 只在i==500时暂停
}

IDEA中右键断点,设置条件:i == 500

# 2、日志断点

不暂停程序,仅记录信息:

  • 右键断点 -> More -> 取消"Suspend"
  • 勾选"Evaluate and log" -> 输入表达式

# 3、异常断点

捕获特定异常:

  • Run -> View Breakpoints -> + -> Java Exception Breakpoints
  • 输入异常类名,如NullPointerException

# 4、方法断点

监控方法进入/退出:

  • 在方法签名行设置断点
  • 可查看参数值和返回值

# 5、字段观察点

监控字段修改:

  • 在字段声明处设置断点
  • 任何修改该字段的代码都会触发

# 七、性能优化建议

# 1、调试对性能的影响

  1. 开销来源:

    • JVMTI接口调用开销
    • 事件通知和处理
    • 网络传输延迟(远程调试)
    • 断点检查开销
  2. 优化策略:

    • 使用条件断点减少触发次数
    • 避免在热点代码设置断点
    • 使用日志断点代替暂停断点
    • 及时禁用不需要的断点
  3. 生产环境注意事项:

    • 避免使用suspend=y
    • 限制调试会话时间
    • 监控JVM性能指标
    • 使用采样式调试工具(如Async Profiler)

# 八、常见问题与解决方案

# 1、无法连接到目标JVM

问题:Connection refused

解决方案:

  • 检查端口是否被占用:netstat -an | grep 5005
  • 确认防火墙规则
  • 验证JDWP参数正确性

# 2、断点不生效

问题:设置的断点没有触发

解决方案:

  • 确保代码已编译(检查class文件时间戳)
  • 验证源码与字节码匹配
  • 检查是否有条件断点表达式错误
  • 确认代码执行路径经过断点

# 3、调试时程序卡死

问题:程序在断点处长时间无响应

解决方案:

  • 检查是否有死锁(jstack分析)
  • 查看是否有大对象导致传输缓慢
  • 适当增加超时时间
  • 使用Evaluate Expression时避免执行耗时操作

# 九、总结

# 1、核心要点回顾

  1. JPDA架构:JDI、JDWP、JVMTI三层协作,提供完整的调试能力
  2. JDI优势:面向对象的高级API,简化调试器开发
  3. JDWP特性:标准化的二进制协议,支持跨平台远程调试
  4. 实践建议:
    • 开发调试工具首选JDI
    • 生产环境慎用远程调试
    • 掌握IDE高级调试功能
    • 注意调试对性能的影响

# 2、扩展学习资源

  • Oracle JPDA文档 (opens new window)
  • JDI API规范 (opens new window)
  • JDWP协议规范 (opens new window)
  • JVMTI编程指南

祝你变得更强!

编辑 (opens new window)
#Java调试#JDI#JDWP
上次更新: 2025/08/15
Java性能分析(Profiler)
Java管理与监控(JMX)

← Java性能分析(Profiler) Java管理与监控(JMX)→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式