Java 调试技巧与 JDB

面试官问:"Java 程序出了问题怎么调试?"

候选人小任答:"用 IDE 的调试器打断点。"

面试官追问:"没有 IDE 的时候怎么调试?"

小任说:"加日志?"

面试官追问:"用 JDB 调试过吗?"

小任答不上来。

【面试官心理】 这道题考查的是候选人的调试能力。能说出 jstack、jmap、jvisualvm、arthas 等工具的候选人,说明有生产环境排查经验。

一、JDB 基础调试 🔴

1.1 启动 JDB

# 方式一:启动时附加
jdb -classpath myapp.jar com.example.Main

# 方式二:attach 到运行中的进程
jdb -attach 12345

1.2 常用命令

# 断点
stop at MyClass:20          # 在 MyClass 第 20 行打断点
stop in MyClass.method      # 在方法入口打断点

# 运行
run                         # 开始运行
cont                        # 继续执行
step                        # 单步执行(进入方法)
next                        # 单步执行(跳过方法)

# 查看
locals                      # 查看本地变量
print variableName          # 打印变量
dump expression             # 查看对象内存
threads                     # 查看线程
where                       # 查看当前堆栈

# 修改
set variableName = value    # 修改变量值

1.3 实际例子

jdb -classpath myapp.jar com.example.Main

# 在 main 方法打断点
stop at Main:10

# 运行
run

# 单步执行
step

# 查看变量
print args
print i

# 查看当前堆栈
where

二、进程诊断工具 🔴

2.1 jps - 查看 Java 进程

jps -l                   # 查看所有 Java 进程及类名
jps -v                   # 显示 JVM 参数

2.2 jstack - 线程堆栈

# 查看线程堆栈
jstack 12345

# 查找死锁
jstack -l 12345 | grep -A 10 "Found one deadlock"

# 常见应用:
# 1. CPU 100%:top -H 找到线程 ID → jstack 找对应线程
# 2. 死锁:直接看到 BLOCKED 线程
# 3. 线程hung:看到 WAITING 状态

2.3 jmap - 内存分析

# 查看堆内存
jmap -heap 12345

# 生成堆转储
jmap -dump:format=b,file=heap.bin 12345

# 查看对象统计
jmap -histo 12345 | head -20

2.4 jstat - 性能统计

# GC 统计
jstat -gc 12345 1000 10   # 每秒一次,共 10 次

# 类加载统计
jstat -class 12345

三、可视化工具 🔴

3.1 jvisualvm

# 启动
jvisualvm

# 功能:
# 1. CPU/内存 profiler
# 2. 线程分析
# 3. 堆转储分析
# 4. OQL 查询

3.2 arthas(阿里诊断工具)

# 启动
java -jar arthas-boot.jar

# 常用命令
dashboard              # 仪表盘
thread -n 3           # 查看前 3 个 CPU 占用的线程
trace 方法          # 方法内部调用耗时
watch 方法          # 观察方法入参/返回值
jad                 # 反编译类

四、远程调试 🟡

4.1 JDWP 配置

# 启动参数
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar myapp.jar

# 或
java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar myapp.jar

4.2 IDEA 远程调试

Run → Edit Configurations → Remote JVM Debug
Host: localhost
Port: 5005

五、生产问题排查 🟡

5.1 CPU 100%

# 1. 找到 CPU 占用最高的线程
top -H -p 12345

# 2. 线程 ID 转十六进制
printf '%x\n' 12346

# 3. jstack 查看
jstack 12345 | grep -A 10 "0x3032"

5.2 OOM 排查

# 1. 添加启动参数
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -jar myapp.jar

# 2. 用 MAT 分析堆转储
jhat heap.bin
# 访问 http://localhost:7000

六、追问升级

面试官:"arthas 的 watch 命令是怎么实现的?"

// arthas 使用 Java Agent 技术
// 1. 加载 Instrumentation API
// 2. 字节码插桩:在方法入口/出口注入代码
// 3. 反射调用:获取参数和返回值

// 类似原理:
// BTrace(已停止维护)
// Byteman