这份材料介绍JAVA的调试技术范围涵盖普通程序和服务器端程序的调试
很多程序员并没有认识到排除软件的错误的价值如果你是一个JAVA开发者就很值得读一读这个材料在现代工具的帮助下开发者成为一个好的调试者和成为一个好的程序员的重要性一样
这个材料假设你已经有基本的JAVA编程的知识如果你精通JAVA这个材料也可以增加你很多知识
如果你有其他语言的调试经验你可以跳过基本知识部分
即使是高级程序员开发的小程序也可能包含错误你只需要理解调试的概念并熟悉合适的工具就可以成为好的调试者这份材料将讲解JAVA调试的基本概念也讨论高级的调试类型我们将浏览不同的技术并且提供一些好的建议去帮助避免追蹤并最终修正程序的错误
我们将通过一个调试范例以使你熟悉调试技术我们也将使用开发源代码工具Jikes 和JDB向你演示如何调试服务器端和客户端程序为了编译和运行范例代码你需要先安装一个Java Development Kit (JDK) 你可以参考后面的部分获得Jikes 和 JDB调试器
关于作者
如果对这个材料的内容有任何问题你可以联系作者Laura Bennett
如果对中文版的翻译有何意见和建议请联系翻译者cherami
Laura Bennett 是IBM的资深软件工程师她获得Pace大学的计算机科学学士学位和Columbia大学的计算机科学硕士学位她是developerWorks的JAVA传教士同时也是站点的建设者在他的空余时间她喜欢和她的Lego MindStorm 机器人玩乐以及和她四岁大的TinkerToys搭建物体
Cherami是一个软件工程师闲暇之余翻译一些计算机文献以期为中国的计算机软件事业做出一点微薄的贡献
调试的基础知识
开始的情况
在JAVA语言的早期一个典型的开发者使用非常陈旧的方法调试程序使用Systemoutprintln() 方法代码的跟蹤信息被打印到控制台文件或者套接字
很少有人能在第一次就写出完美的(没有任何错误)代码因此市场认识到了对于像C++ 程序员使用的调试器那样的工具的需要Java开发者现在有很多调试工具可以选择选择什么样的工具依赖于你的技术等级通常新手使用GUI调试工具而有更多经验的程序员趋向于避免使用所见即所得的工具而更关心有更多的控制能力没有哪个开发者不使用任何调试工具调试器允许你穿越代码冻结输出以及检查变量开发者越有经验调试工具越可以帮助他更快定位程序问题的位置
Java调试器的类型
这里有几种Java调试技术的工具:
IDE(集成开发环境) 包含它们自己的调试器 (例如IBM的VisualAge for Java Symantec Visual Café以及 Borland JBuilder)
单独的GUI工具 (例如Jikes Java 平台调试器 javadt 以及JProbe)
基于文本和命令行的工具 (例如Sun JDB)
野蛮的使用编辑器 (例如Notepad 或者 VI) 检查堆栈描绘(stack traces)
你使用的 JDK JSDI JSP 和HTML对你的选择都有影响
IDE 和独立的GUI 调试器对于初学者是最容易的并且被证明是最节省时间的调试器将引导你到程序崩溃的地方在调试器里面执行程序使用鼠标设置断点并穿越代码使用这些调试器的不利方面是并非所有的IDE调试器都支持最新的Java API和技术 (例如servlets 和 EJB 组件)
基于文本和野蛮的使用编辑器的技术提供更多的控制但是对于没有太多经验的程序员可能会花费更长的时间找出错误我们称它们为可怜人的调试方法
如果上面的都不满足你的需求 Java平台引入Java Debugging APIs使你可以创建符合你自己特定需求的调试器
调试类型
这儿有很多调试方法无论是在客户端还是服务器端我们在这个材料里面包含下面的方法:
基本的Java字节码 (也就是使用Systemoutprintln())
使用注释
附加在一个正在运行的程序上
远程调试
需求调试(Debugging on demand)
优化代码的调试
Servlet JSP 文件以及EJB 组件的调试
在后面会详细说明每一种类型的调试
共同的错误类型
为了给你一个你将遇到什么的提示我们在下面列出了开发者一次又一次遇到的编辑或句法错误 是你最先和最容易遇到的错误它们通常是键入错误引起的
逻辑错误 不同于运行时错误因为没有任何异常被抛出但是输出不是期望的东西这些错误的范围从缓沖区溢出到内存洩漏
运行时错误 在程序执行时发生并且通常产生一个Java异常
线程错误 是最难重复和跟蹤的
Java debugging APIs
Sun已经定义了调试的结构它们称之为JBUG这是为了回应对真正的Java调试器的需要做出的这些APIs帮助程序员建立符合自己需要的调试器:
接口应该和语言的风格一样是面向对象的
例如线程和监视器这样的Java运行时特性应该被前面的支持
可以进行远程调试
在通常操作下的安全性不能被损害
修正的Java Debugger (JDB) 既是体现Java Debugging API的概念同时又是一个有用的调试工具它用Java Debug Interface (JDI)重写并且是JDK的一部分 JDB将在后面详细讨论
准备一个调试用的程序
Java平台为调试过程提供语言支持
你在用编译器编译你的程序时可以用编译选项指示编译器在目标文件中产生符号信息如果你使用其它的编译器而不是javac参考你的编译器的文档获得如何生成带有调试信息的目标文件
如果你使用javac 编译器创建调试代码使用g 编译选项这个选项让你在调试的时候可以检查本机类实例和静态变量如果你没有使用该选项生成你的类文件你也可以设置断点和追蹤代码但是你将不能检查变量(断点是手工指定的程序运行停止的点)
即使你使用g选项编译你的程序也不能调试JAVA平台的核心系统类的局部变量如果你需要列出某些系统类的局部变量的列表你需要使用g选项编译这些类也就是使用g选项重新编译rtjar 的类或者是 srczip 里面的文件然后指定你的 classpath 为正确的类文件使你用新编译的类运行你的程序在Java 下使用 boot classpath 选项使得新类被首先加载
记住如果你使用 O 选项优化你的代码你就不能调试你的类优化会将所有的调试信息从类中去掉
注意: 检查你的 CLASSPATH 环境变量是正确的才能让调试器和Java 程序知道在哪儿寻找你的类库你也应该检查你的调试工具看是否需要其它的什么或者是环境变量
设置断点
调试的第一步就是找到代码出错的位置断点设置能帮你完成这个
断点是你你放置在程序里面的临时标记它使得调试器知道在哪儿停止程序的执行例如如果程序里面的某个申明引发问题你可以将断点设置在包含那个申明的行上然后运行程序在那个申明被执行前程序停止执行然后你可以检查变量寄存器存储器以及堆栈的内容然后跨过(或执行)那个申明查看问题是怎么引起的
不同的调试器支持不同的断点一些通用的类型是:
行断点 在程序特定行的代码被执行前被引发
方法断点 在到达被设置成断点的方法时被引发
计数断点 在某个计数器达到或超过某个特定值时被引发
异常断点 在代码抛出一个特定异常时被引发
储存变化断点 在存储在特定地址范围的内容被修改时引发
地址断点 在被设置成断点的地址达到时被引发
注意: 一些调试器只在编译版本的Java代码 (使用justintime 编译器生成的代码) 上支持某些断点类型而不支持解释代码(使用javac 工具生成的代码)一个例子就是地址断点每个工具在你能设置断点的方式上可能有些不同检查你的工具的文档
你可能会问我如何知道在哪儿放置断点?
如果你对这个问题完全没有感觉你可以在main() 方法的开始设置断点
如果你的代码产生堆栈复写(stack trace) 在程序产生它的地方设置断点你将在堆栈复写里面看到源代码中出问题的行号
如果你的输出或者图形显示的特定部分没有正确的显示预定信息(例如文本域显示错误的文本)你可以在该组件被创建的地方设置断点然后你可以单步执行你的程序显示和GUI对象相关的值
经验将在最合适的地方设置断点你在一个类或者程序里面可以设置多个断点
通常你在调试代码的时候会禁止激活添加删除断点工具会允许你查看你所设置的所有断点的位置同时给你一次删除所有断点的选项
单步执行程序
单步执行程序是最终解决那些棘手的调试问题的方法它允许你追蹤类里面的方法体的整个执行过程注意你不需要设置断点就可以停止一个GUI程序的执行
设置断点后在调试器里面开始执行程序当遇到第一个断点后你可以越过申明进入方法体或类体也可以继续运行直到下一个断点或程序结束
在调试程序的时候经常遇到的术语有
进入 执行当前行如果当前行包含一个方法调用执行被调用方法的第一行如果类中的方法是用不带调试信息的选项编译的 (也就是没有使用 g 选项)