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 !"
✅ print
和 eval
在 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 场景
- ✅ 掌握
stop
、print
、cont
等核心命令即可高效调试
虽然日常开发多用 IDEA 或 Eclipse,但掌握原生工具在某些受限环境(如 Docker、生产服务器)下非常关键。遇到问题别只会看日志,上调试器才是王道。
📌 更多细节可参考官方文档: