目前可以用来调试Java应用程序的工具令人叹为观止。现代IDE提供了全功能的调试界面,并且像GlassBox这样的工具用BCI(bytecode instrumentation)提供了关于应用程序运行时状态的更多信息。虽然你有时候会发现自己处于无法使用这些工具的境地,或许应用程序被部署在防 火墙的另一端,不允许你远程调试;或许你正在客户那里,没有你喜欢的工具可用。现代调试工具使用的大多数信息和方法都由JVM自己提供,并具有控制这种信 息而不用最新图形工具的一些方法。本文关注一些可用的调试方法,它们只使用Java SDK(JDK)自带的那些工具。
JDB
JDK携带的主要调试工具是jdb,这是一种命令行调试器,它将附到一个在运行的VM,并允许你设置断点(breakpoint)、逐步跟踪代码、检验变量并评估表达式的值。JRE没有包括JDB,因此你需要安装JDK。
如果你知道如何利用IDE中内建的任何调试器调试Java应用程序,你就可以迅速地学会利用JDB进行调试。为了允许JDB或者任何调试器连接到一个在运 行的JVM,JVM必须通过许多命令行参数来启动。这些参数促成JVM去加载和初始化调试库,配置传输并打开一个socket。例如,要配置VM在端口 8000监听调试连接,你应该使用类似于下面的命令行选项:
-Xdebug -Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=8000
你现在可以利用下面的命令行用JDB连接:
jdb -attach 8000
或者在Windows上:
jdb -connect com.sun.jdi.SocketAttach:port=8000
一旦连接上,你应该看到JDB提示。
你现在连接到了JVM,可以开始调试你的应用程序了。你想要做的第一件事可能是设置一些断点。在JDB中设置断点有三种方法。
1、在命名方法的项上设置断点。
2、在指定的源代码行数上设置断点。
3、在发生特定异常的时候。
当你的应用程序触发断点时,JVM将中止执行,并等待你告诉它接下来要利用JDB做什么。你可以跳过去,进入到方法调用里面,检查本地变量,并评估表达式,为了获得一个可用命令的列表,可以输入help来得到它。
虽然利用JDB调试比用为你显示源代码行的图形调试器更慢,但是只要你访问源代码,就可以获得与利用JDB相同的效果。
不建议你通过调试参数运行产品应用程序,因为使用这些参数的时候,有一个性能和安全问题。但在测试、开发期间以及检查某类产品问题的时候,JDB都是一种非常有用的工具。
关于JDB的更多信息,请见Sun的文档:http://java.sun.com/j2se/1.3/docs/tooldocs/win32/jdb.html 。
利用Thread Dump
现代的Java和Jave EE应用程序有许多线程,它们全都彼此交互,并连接到外部系统。Java提供了一种获取在VM中运行的所有线程的调用堆栈快照的方法。这个的输出称作Thread Dump,它是大量的堆栈轨迹(每个线程一个),以及关于线程优先级和它持有或等待的任何锁或监视器的信息。
生成Thread Dump在不同的操作系统中做起来也不同。在Windows中,是在JVM运行的控制台窗口中按下Ctrl-Break。对基于Linux和Unix的系统,则用kill命令将SIGQUIT发送到JVM。这可以通过命令kill – 3 完成,这里的pid是JVM的进程ID。
现代的企业Java应用程序中,经常有大量的线程,它们中有许多都没起任何作用。你可以用许多种方式来辨别重要的线程。
◆它们通常具有较长的相关堆栈轨迹。
◆搜索已知的包名称(例如你应用程序中使用的包)。
线程持有或者等待的任何锁或者监视器都在轨迹中指定,因此死锁以及其他同步相关的问题,都可以通过分析哪些线程在等待或者持有哪些锁和监视器进行辨别。
在性能调优或者调试慢运行应用程序时,它对相隔几秒钟生成许多Thread Dump并对它们进行比较会很有利。这样让你知道应用程序的哪些部分正在缓慢地运行着,而不必使用更高级的性能分析工具。
JMX
从Java SE 5开始,Sun给JVM运行时信息提供了JMX API作为Java SE的一部分,Sun还提供了一种工具,用来在一个正运行的JVM内部查询MBeans。这个工具是jconsole,并且就像jdb一样,它是与JDK捆绑在一起的。
为了允许jconsole连接到你的VM,你需要增加许多命令行参数。
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=20001
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
当你运行jconsole时,它将在你的机器上侦测VM运行,并问你想要连接到哪一个。一旦你连接上了,摘要(Summary)屏幕就会显示大量关于VM的信息,让你访问使用关于它线程的运行时信息、堆的使用(Heap Usage),以及所有变得能通过JMX MBeans使用的信息。通过将MBeans增加到你的应用程序中,有可能使你的应用程序信息能通过JMX控制台以及用JMX接口收集它们信息的其他工具来使用。利用JMX将监视功能增加到你的应用程序,则是另一篇文章的主题了。
关于使用jconsole的更多信息,请见:http://java.sun.com/j2se/1.5.0/docs/guide/management/jconsole.html 。
信息唾手可得
这些工具虽然不完整,或者不像你习以为常的工具那么容易使用,但它们仍然提供了很多你可以用来分析和调试你应用程序的信息。基本熟悉了它们之后,就可以快速解决问题,否则要花数天进行调查,因此值得花时间去体验它们,并了解它们是如何工作的。
用Java 这么久,说来惭愧,大部分情况下使用的调试语句是:System.out.println("value:"+value);这种形式,这两天看了些关于java调试技巧的东西,总结一下,以后要积极使用:
一:一种很简单的方法就是使用一个boolean量如:debugMode,需要看看程序的输出时可以使用:if(debugMode)System.out.println("..");
在项目完毕后把debugMode变量设为false;
这种方法的不足是:为了启动或者禁止debug,必须改动代码
二:
一个比上述方法稍好的是:使用java -D:如:java -Ddebug=true MyClass,
在使用这种方法时,必须在你所有的class中添加System.getProperty("debug")来取得调试标志,这样第一种方法的使用,就改为:
public static final boolean debug;
static{
String sDebug=System.getProperty("debug");
if(sDebug!=null && sDebug.equalsIngnoreCase("true"))
debug=true;
else
debug = false;
}
这样在不更改source code的情况下可以直接修改调试标志,但是这样不够灵活,比如你不能指定哪一部分使用调试功能,而只是
要使用调试功能就必须在代码的全部使用,这样就引出了第三种调试方法,可以定义一个用于调试的类。
三:
public class DebugManager {
public static final String SYSTEM_DEBUG_KEY="system.Debug";
public static final boolean debug;
static {
debug = toBoolean(SYSTEM_DEBUG_KEY);
}
private static boolean toBoolean(String key){
boolean debug;
String sDebug = System.getProperty(key);
if(sDebug != null && sDebug.equalsIgnoreCase("true"))
debug = true;
else
debug =false;
return debug;
}
public static boolean getSystemDebug(){
return debug;
}
public static boolean getSystemDebug(String applicationKey) {
if(debug || toBoolean(applicationKey))
return true;
else
return false;
}
}
这样对于一个application(Test)可以着牙功能调用:
boolean debug =DebugManager.getSystemDebug(applicationKey);
要打开Test的调试功能,只需:
java -DTest.Debug=true Test
当然这个类可以进行进一步扩展,比如将其变为日志管理,控制调试信息的输出位置等
四:
使用断言
断言是Merlin(JDK 1.4)中一项新功能,默认情况下不使用断言,但是programmer可以在任何时候enable或者disable这项功能,
从而可以在测试时启用断言验证,而在部署时禁用断言验证,而程序运行时若用户碰到问题时再重新启用断言。
断言检查,失败时不会抛出Exception,而是抛出AssertionError,
1,断言的两种语法形式:
(1)assert booleanExpression;
(2) assert boolExpression:message; //message将作为错误抛出后的结果显示
assert必须位AssertTest
于可执行快中,不能将assert和实例的类变量一起使用,但可以将其放在任何方法内。
2,启用断言:
编译:javac -source 1.4 AssertTest.java
运行:java -ea AssertTest (参数)
-ea是启用断言的简易方法,但是-ea不能用于系统类,
(1)若要对系统类使用断言:则需:-enablesystemassertions,或者-esa
(2)若要对这个package启用断言:要在-ea后加(:,package的名字,...)如对test.util包及其所有的
子包启用断言,则可以java -ea:test.util... AssertTest