
1. 理解JDB的单步执行行为
java debugger (jdb) 是一个命令行工具,用于调试java应用程序。在进行程序调试时,开发者通常期望在单步执行(step)时能直接看到当前即将执行的源代码行,这对于理解程序流程至关重要。然而,jdb的默认step命令行为可能与此预期有所不同。
例如,对于以下Java代码:
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
System.out.println("Hello world! line 2");
System.out.println("Hello world! line 3");
}
}当我们在JDB中设置断点并执行step命令时,可能会观察到如下输出:
> stop at Main.main > run VM Started: Set deferred breakpoint Main.main Breakpoint hit: "thread=main", Main.main(), line=3 bci=0 main[1] step Step completed: "thread=main", Main.main(), line=4 bci=8 main[1] step Step completed: "thread=main", Main.main(), line=5 bci=16
从上述输出可以看出,step命令确实指示了当前执行的线程、方法名以及行号(line=4、line=5),但并没有直接显示这些行号对应的源代码内容,例如 System.out.println("Hello world! line 2");。这可能让习惯于图形化调试器(如IDE内置调试器)的开发者感到困惑。
2. 解决方案:使用list命令查看源代码
JDB提供了一个专门的命令list来显示当前执行位置附近的源代码。无论您是通过step、next、cont等命令暂停在某个位置,都可以使用list来查看上下文代码。
立即学习“Java免费学习笔记(深入)”;
2.1 list命令的基本用法
在JDB提示符下,直接输入list,它会显示当前执行行周围的源代码。默认情况下,它通常会显示当前行以及前后几行代码,以提供足够的上下文信息。
示例:结合list命令进行调试
用eclipse开发android程序的时,跟VS一样是可以断点单步调试的。 Eclipse Java编辑器不但能够为开发者提供代码编写、语法纠错和实时编译等常用功能,而且还能够对Java源代码进行快速修改、重构等高级操作。感兴趣的朋友可以过来看看
-
准备Java源代码 Main.java:
public class Main { public static void main(String[] args) { System.out.println("Hello world!"); // Line 3 System.out.println("Hello world! line 2"); // Line 4 System.out.println("Hello world! line 3"); // Line 5 } } -
编译源代码(重要提示:务必包含调试信息): 为了让JDB能够获取到源代码行号和变量信息,编译时必须使用-g选项。
javac -g Main.java
-
启动JDB调试器:
jdb Main
-
在JDB中进行调试:
$ jdb Main Initializing jdb ... > stop at Main.main // 在main方法入口设置断点 Deferring breakpoint Main.main. It will be set after the class is loaded. > run // 运行程序 run Main Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable > VM Started: Set deferred breakpoint Main.main Breakpoint hit: "thread=main", Main.main(), line=3 bci=0 // 断点命中,在第3行 main[1] list // 使用list命令查看当前位置代码 1 public class Main { 2 public static void main(String[] args) { 3 => System.out.println("Hello world!"); 4 System.out.println("Hello world! line 2"); 5 System.out.println("Hello world! line 3"); 6 } 7 } main[1] step // 单步执行到下一行 Step completed: "thread=main", Main.main(), line=4 bci=8 // 停在第4行 main[1] list // 再次使用list命令查看 1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("Hello world!"); 4 => System.out.println("Hello world! line 2"); 5 System.out.println("Hello world! line 3"); 6 } 7 } main[1] step // 继续单步执行 Step completed: "thread=main", Main.main(), line=5 bci=16 // 停在第5行 main[1] list // 再次查看 2 public static void main(String[] args) { 3 System.out.println("Hello world!"); 4 System.out.println("Hello world! line 2"); 5 => System.out.println("Hello world! line 3"); 6 } 7 }通过上述示例,我们可以清晰地看到,在每次step之后,使用list命令可以有效地显示当前执行位置的源代码,并且用 => 符号明确指示了当前行。
2.2 list命令的其他变体
list命令还支持一些参数,以提供更灵活的源代码查看方式:
- list
:显示指定行号周围的源代码。 - list
, :显示指定行号范围内的源代码。
3. 注意事项与最佳实践
- 编译时保留调试信息 (-g): 这是使用JDB进行有效调试的基石。如果编译时没有包含调试信息,JDB将无法获取到准确的行号、变量名等,导致list命令可能无法正常工作,或者只能显示字节码指令(bci)而不是源代码行。
- JDB的局限性: JDB是一个基础的命令行调试器,相比于现代IDE提供的图形化调试界面,其功能和用户体验较为原始。对于复杂的调试任务,通常推荐使用IDE的调试功能。
-
其他常用JDB命令:
- print
:打印变量的值。 - locals:显示当前方法的所有局部变量。
- where:显示当前线程的堆栈跟踪。
- cont:继续执行直到下一个断点或程序结束。
- next:单步执行,但不进入方法调用内部(跳过方法)。
- step up:执行完当前方法并返回到调用点。
- clear
:清除断点。 - exit 或 quit:退出JDB。
- print
4. 总结
JDB在单步执行时不会默认显示源代码行,但通过巧妙地结合使用list命令,开发者可以随时查看当前执行位置的源代码上下文,从而弥补这一不足。记住在编译时使用-g选项是确保JDB正常工作的关键。掌握list以及其他JDB命令,将有助于您在没有图形界面的情况下,也能有效地进行Java应用程序的命令行调试。









