摘要
Java 平台提供了用于调试处理的语言支持。可以用某些编译器选项来编译程序,这些编译器选项指导编译器在目标文件中生成符号化信息。假如所使用的编译器不是javac,请参考编译器文档以了解如何使用调试信息来编译程序。
--------------------------------------------------------------------------------
Java调试教程--基础知识概述
SUN Laura Bennett
预备要调试的程序
Java 平台提供了用于调试处理的语言支持。可以用某些编译器选项来编译程序,这些编译器选项指导编译器在目标文件中生成符号化信息。假如所使用的编译器不是javac,请参考编译器文档以了解如何使用调试信息来编译程序。
假如使用 javac 编译器来构建要调试的代码,请使用 -g 编译器选项。该选项让您可以在调试时检查局部、类实例和静态变量。假如不使用 -g 选项来编译类,依然可以设置断点并单步调试代码;但是将不能检查变量。(断点是在代码中手工指定的希望停止执行程序的位置。)
即便使用 -g 选项来编译应用程序,在核心 Java 平台系统类中仍不能使用本地调试值。假如需要列出某些系统类的列表局部变量,需要使用 -g 选项来编译那些类。使用-g 选项来重新编译 rt.jar 或 src.zip 文件中的类。将类路径指向正确的类文件,以便使用新编译的对话类来运行应用程序。在 Java 2 中,使用 boot classpath 选项,这样?请记住,假如使用用于优化代码的 -O 选项进行编译,那么将无法调试类。优化将除去类中的所有调试信息。
注:验证是否正确设置了 CLASSPATH 环境变量,以便调试器和 Java 应用程序知道在哪里可以找到类库。还应该检查正在使用的工具,查看是否还有其它必需或可选的环境变量。
设置断点
调试的第一步是找出代码的哪个位置出了错。设置断点有助于完成此项工作。断点是放置在程序中的临时标记,用于告诉调试器在何处停止执行程序。例如,假如程序中的某个特定语句造成了问题,可以在包含该语句的那行中设置断点,然后运行程序。在执行该语句之前,将在断点处停止执行。然后可以检查变量、寄存器、存储器和堆栈的内容,然后跨过(或执行)该语句来查看问题是如何产生的。许多调试器都支持几种断点。某些最常用的断点是:
行断点,在执行程序中某特定行的代码之前触发。
方法断点,在到达已设置了断点的方法时触发。
计数器断点,在计数器假设或超过某个特定值时触发。
异常断点,在代码抛出特定类型的异常时触发。
存储器更改断点,在更改了某特定存储器地址范围中的存储器时触发。
地址断点,在到达设置了断点的地址处触发。
注:某些调试器只支持编译型 Java 代码(如 JIT 编译器)中某些类型的断点,而不支持解释型代码(如由 javac 工具编译的代码)中的断点。“地址断点”就是这样的例子。每种工具设置断点的方法都略有不同。请查看工具的文档。
您也许会问,我怎么知道在哪里放置断点呢?
假如您对此问题仍感到不知所措,那么可以在 main() 方法中的程序开始处设置断点。
假如代码生成了堆栈跟踪,那么在堆栈跟踪结束的区域中设置断点。可以在堆栈跟踪中看到源代码的行号。
假如输出或图形显示的特定部分没有正确显示信息(例如,文本字段显示了错误的文本),可以在创建组件的位置设置断点。然后可以单步调试代码,并显示分配给 GUI 对象的值。
经验会教您如何在最适当的位置设置断点。在一个类或程序中可以设置多个断点。在调试代码时,通常会禁用或启用断点,或者添加或删除断点。工具可以让您查看所有断点的位置,并可以在一步操作中删除所有断点。
单步调试程序
单步调试程序就是解决这些棘手的调试问题的基本方法。它让您可以运行在类主体中调用的方法。请注重,不必设置断点就可以停止图形用户界面 (GUI) 程序。在设置了断点之后,开始在调试器中执行代码。碰到第一个断点时,可以跨过语句,步入其它方法或类,继续运行直至到达下一个断点,或者继续运行直到程序结束。步入调试器时,会看到的常见项是:
Stepping into 执行当前行。假如当前行包含对某个方法的调用,则执行转到被调用方法的第一行。假如方法在未用调试信息编译(即,用 -g 选项)的类中,则会看到一条消?
Stepping over 执行当前行,但不会在该行中调用的任何函数或例程中停止。
Step return 从当前执行点一直执行到调用当前方法的那行代码的后面一行。
检查变量
通常,程序会因为没有正确设置变量的值而发生信息转储。最常见的情况是试图将变量与空值比较或对变量除以零。找出是否存在这种情况的最简便方法是在发生错误的位置检查值。通常,不会在您期望的位置对变量进行赋值。
可视调试器通常有一个监控窗口,在该窗口中显示了当前所在的类中所有局部变量的值。某些调试器甚至显示变量的地址,甚至还让您动态更改变量值以查看程序是否按您的最初设想继续执行。命令行调试器通常通过命令来处理相同功能。使用命令行功能,甚至可以通过显示每一行和每一列的内容来检查数组的全部内容。
虽然大多数调试器只在监控窗口中显示类作用域中的局部变量,但有些调试器让您可以在它离开作用域之后继续监控变量。某些调试器支持查看寄存器。请注重,只能在编译型 Java 程序中查看寄存器,在解释型程序却不行。
堆栈跟踪
当 Java 程序转储信息时,它会生成控制台窗口的堆栈跟踪。堆栈跟踪告诉开发人员程序执行到发生问题的位置经过的确切路线。堆栈跟踪将指出类和方法名称,以及源代码行号(假如使用调试选项进行编译)。假如从跟踪的起点开始,可以向回追溯代码,以查看究竟执行了什么语句。这是一种迅速确定程序中哪里出错的方法。
可以使用以下语句人为地生成堆栈跟踪。
Throwable().printStackTrace() 用于在某一时刻生成方法代码的跟踪。此跟踪将显示线程之间的方法调用。
Thread.currentThread.dumpStack() 用于只生成当前线程的快照。
假如需要了解程序在什么情况下会生成输出,就应该实施堆栈跟踪。以下是实施堆栈跟踪的一个示例。此代码段方法创建了文件的副本。我们通过比较两个文件的长度来检查复制是否成功。假如不相等,我们将对一个建立跟踪,然后打印堆栈跟踪(见以粗体字显示的语句)。Throwable() 是 java.lang 包中的一个类。printStackTrace() 是 Throwable() 类中的一个方法,它将打印出程序执行路线的跟踪。
public static boolean copyFile( String sourceFile, String targetFile)
{
........
........
// see if the copy sUCceeded.
if (success)
{
// see if the correct number of bytes were copied
long newFileLength = new File(targetFile).length();
if (oldFileLength != newFileLength)
{
Debug.trace(1, sourceFile + Constants.BLANK_STRING +
Long.toString(ol
dFileLength));
Debug.trace(1, targetFile + Constants.BLANK_STRING +
Long.toString(ne
wFileLength));
Throwable().printStackTrace();
return false;
}
}
else
{
Debug.trace(1, sourceFile);
Debug.trace(1, targetFile);
return false;
}
........
........
return true;
}
您可能会发现堆栈跟踪没有打印行号。这就是所谓的“编译代码”。要获取行号,使用 nojit 选项或命令行自变量 Djava.compiler=NONE 来禁用 JIT 编译器。但是,假如得到了方法的名称以及它所属的类,获取行号就不那么重要了。
诊断方法
Java 语言提供了 Runtime() 类中的方法,用于跟踪对 Java 虚拟机的调用。这些跟踪将生成一个列表,它包含程序对 JVM 字节码的每一个方法调用。请注重,该列表可能会生成大量输出,所以每次使用它时应针对小段代码。将以下行添加到源代码中,以打开跟踪:
traceMethodCalls(true)
将以下行添加到源代码中,以关闭跟踪:
traceMethodCalls(false)
启动 JVM,当它被发送到标准输出时,监视输出。