1. 概述

远程调试 Java 应用在多种场景下都非常实用,比如生产环境问题排查、容器化部署调试、或本地无法复现的线上 Bug 分析。

本文将带你使用 JDK 自带的工具,实现对远程运行的 Java 程序进行本地调试。✅ 整个过程无需额外依赖,纯原生支持,简单粗暴又高效。


2. 示例应用

我们先写一个简单的 Java 程序作为调试目标。这个应用会不断创建对象并输出实例字段,便于我们设置断点观察:

public class OurApplication {
    private static String staticString = "Static String";
    private String instanceString;

    public static void main(String[] args) {
        for (int i = 0; i < 1_000_000_000; i++) {
            OurApplication app = new OurApplication(i);
            System.out.println(app.instanceString);
        }
    }

    public OurApplication(int index) {
        this.instanceString = buildInstanceString(index);
    }

    public String buildInstanceString(int number) {
        return number + ". Instance String !";
    }
}

编译时记得加上 -g 参数,保留完整的调试信息(如行号、局部变量表等),否则调试时会“找不到北”:

javac -g OurApplication.java

⚠️ 踩坑提醒:没有 -g 编译的 class 文件,调试时无法查看局部变量,断点也可能失效。


3. JDWP:Java 调试通信协议

Java Debug Wire Protocol(JDWP) 是 JVM 提供的调试通信协议,用于调试器(debugger)与被调试程序(debuggee)之间的交互。

  • debuggee:被调试的 Java 应用(目标进程)
  • debugger:发起调试的客户端,比如 JDB、IDEA、Eclipse

两者可以运行在同一台机器,也可以跨网络。本文重点讲远程调试场景。

3.1. JDWP 启动参数详解

通过 JVM 启动参数 agentlib:jdwp 来启用 JDWP,常用选项如下:

参数 说明
transport=dt_socket ✅ 推荐。基于 TCP socket 通信,跨平台,支持远程调试
transport=dt_shmem ❌ 仅限 Windows,且必须同机,进程间共享内存
server=y 当前应用作为服务端,等待调试器连接
server=n 当前应用作为客户端,主动连接调试器(反向连接)
suspend=n 启动后不暂停,直接运行(适合线上)
suspend=y 启动后暂停,等待调试器连接后再继续
address=8000 监听端口。Java 9+ 需注意绑定地址问题

3.2. 启动命令示例

启动远程应用,开启调试支持:

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

输出示例:

Listening for transport dt_socket at address: 8000

⚠️ 注意:Java 5 之前的写法是 -Xdebug -Xrunjdwp:...,虽然仍兼容,但已过时,建议使用新的 agentlib 语法。

3.3. Java 9 及以上版本的地址变更

从 Java 9 开始,address=8000 默认只绑定 localhost,无法从外部访问。这是安全增强,但对远程调试是个坑。

要让远程机器能连接,必须显式指定 IP 或使用通配符:

# 允许所有 IP 连接(测试环境可用,生产慎用)
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000 OurApplication

# 推荐:绑定具体 IP(更安全)
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=192.168.1.100:8000 OurApplication

✅ 最佳实践:生产环境建议绑定内网 IP,避免暴露在公网。


4. JDB:命令行调试器

JDB(Java Debugger) 是 JDK 自带的命令行调试工具,适合无 GUI 环境或快速验证。

连接远程应用(假设运行在本机):

jdb -attach 127.0.0.1:8000

输出:

Initializing jdb ...
>

连接成功后,进入交互模式,可以开始调试。

4.1. 设置断点

使用 stop 命令设置断点:

# 在构造函数上设断点
> stop in OurApplication.<init>

# 在 main 方法设断点(注意 String 的全限定名)
> stop in OurApplication.main(java.lang.String[])

# 在实例方法设断点
> stop in OurApplication.buildInstanceString(int)

# 在第 7 行设断点(打印语句那行)
> stop at OurApplication:7

📌 注意:

  • stop in:按方法签名设断点
  • stop at:按行号设断点

设置后,当程序运行到断点处,JDB 会输出:

Breakpoint hit: "thread=main", OurApplication.main(), line=7 bci=17

4.2. 单步执行与变量查看

断点触发后,可以使用以下命令:

命令 作用
cont 继续执行,直到下一个断点
step 单步执行,进入方法内部
next 单步执行,不进入方法
print / eval 查看变量或表达式值

示例操作:

# 查看静态字段
> eval OurApplication.staticString
OurApplication.staticString = "Static String"

# 查看实例字段
> eval app.instanceString
app.instanceString = "68741. Instance String !"

# 查看局部变量(无需前缀)
> print i
i = 68741

# 执行表达式(会创建新对象)
> print new OurApplication(10).instanceString
new OurApplication(10).instanceString = "10. Instance String !"

printeval 在 JDB 中功能基本一致,可互换使用。

4.3. 删除断点

使用 clear 删除断点:

# 删除第 7 行的断点
> clear OurApplication:7
Removed: breakpoint OurApplication:7

# 查看剩余断点
> clear
Breakpoints set:
        breakpoint OurApplication.<init>
        breakpoint OurApplication.buildInstanceString(int)
        breakpoint OurApplication.main(java.lang.String[])

全部调试完成后,输入 quit 退出 JDB。


5. 总结

本文演示了如何使用 JDWP + JDB 完成 Java 应用的远程调试:

  • ✅ JDWP 通过 agentlib:jdwp 启用,支持跨平台远程调试
  • ✅ Java 9+ 需注意 address=*:port 才能远程访问
  • ✅ JDB 是轻量级命令行调试器,适合无 IDE 场景
  • ✅ 掌握 stopprintcont 等核心命令即可高效调试

虽然日常开发多用 IDEA 或 Eclipse,但掌握原生工具在某些受限环境(如 Docker、生产服务器)下非常关键。遇到问题别只会看日志,上调试器才是王道。

📌 更多细节可参考官方文档:


原始标题:Java Application Remote Debugging | Baeldung