@@ -1309,7 +1309,7 @@ public class SLF4JLevels {
13091309
13101310使用调试器,可以展示任何时刻的程序状态,查看变量的值,一步一步运行程序,连接远程运行的程序等等。特别是当你构建较大规模的系统(bug 容易被掩埋)时,熟练使用调试器是值得的。
13111311
1312- #### 使用 JDB 调试
1312+ ### 使用 JDB 调试
13131313
13141314Java 调试器(JDB)是 JDK 内置的命令行工具。从调试的指令和命令行接口两方面看的话,JDB 至少从概念上是 GNU 调试器(GDB,受 Unix DB 的影响)的继承者。JDB 对于学习调试和执行简单的调试任务来说是有用的,而且知道只要安装了 JDK 就可以使用 JDB 是有帮助的。然而,对于大型项目来说,你可能想要一个图形化的调试器,这在后面会描述。
13151315
@@ -1359,10 +1359,124 @@ SimpleDebugging.main(SimpleDebugging.java:20)
13591359
13601360首先看方法 `foo3()`,问题很明显:除数是 0。但是假如这段代码被埋没在大型程序中(像这里的调用序列暗示的那样)而且你不知道从哪儿开始查找问题。结果呢,异常会给出足够的信息让你定位问题。然而,假设事情更加复杂,你必须更加深入程序中来获得比异常提供的更多的信息。
13611361
1362+ 为了运行 JDB,你需要在编译 **SimpleDebugging.java** 时加上 **-g** 标记,从而告诉编译器生成编译信息。然后使用如下命令开始调试程序:
1363+
1364+ **jdb SimpleDebugging**
1365+
1366+ 接着 JDB 就会运行,出现命令行提示。你可以输入 **?** 查看可用的 JDB 命令。
1367+
1368+ 这里展示了如何使用交互式追踪一个问题的调试历程:
1369+
1370+ **Initializing jdb...**
1371+
1372+ **> catch Exception**
1373+
1374+ `>` 表明 JDB 在等待输入命令。命令 **catch Exception** 在任何抛出异常的地方设置断点(然而,即使你不显式地设置断点,调试器也会停止— JDB 中好像是默认在异常抛出处设置了异常)。接着命令行会给出如下响应:
1375+
1376+ **Deferring exception catch Exception.**
1377+
1378+ **It will be set after the class is loaded.**
1379+
1380+ 继续输入:
1381+
1382+ **> run**
1383+
1384+ 现在程序将运行到下个断点处,在这个例子中就是异常发生的地方。下面是运行 **run** 命令的结果:
1385+
1386+ **run SimpleDebugging**
1387+
1388+ **Set uncaught java.lang.Throwable**
1389+
1390+ **Set deferred uncaught java.lang.Throwable**
1391+
1392+ **>**
1393+
1394+ **VM Started: In foo1**
1395+
1396+ **In foo2**
1397+
1398+ **In foo3**
1399+
1400+ **Exception occurred: java.lang.ArithmeticException**
1401+
1402+ **(uncaught)"thread=main",**
1403+
1404+ **SimpleDebugging.foo3(),line=16 bci=15**
1405+
1406+ **16 int i = 5 / j**
1407+
1408+ 程序运行到第16行时发生异常,但是 JDB 在异常发生时就不复存在。调试器还展示了是哪一行导致了异常。你可以使用 **list** 将导致程序终止的执行点列出来:
1409+
1410+ **main[1] list**
1411+
1412+ **12 private static void foo3() {**
1413+
1414+ **13 System.out.println("In foo3");**
1415+
1416+ **14 int j = 1;**
1417+
1418+ **15 j--;**
1419+
1420+ **16 => int i = 5 / j;**
1421+
1422+ **17 }**
1423+
1424+ **18 public static void main(String[] args) {**
1425+
1426+ **19 foo1();**
1427+
1428+ **20 }**
1429+
1430+ **21 }**
1431+
1432+ **/ * Output : **
1433+
1434+ 上述 `= > ` 展示了程序将继续运行的执行点。你可以使用命令 ** cont** (continue ) 继续运行,但是会导致 JDB 在异常发生时退出并打印出栈轨迹信息。
1435+
1436+ 命令 ** locals** 能转储所有的局部变量值:
1437+
1438+ ** main[1 ] locals**
1439+
1440+ ** Method arguments: **
1441+
1442+ ** Local variables: **
1443+
1444+ ** j = 0 **
1445+
1446+ 命令 ** wherei** 打印进入当前线程的方法栈中的栈帧信息:
1447+
1448+ ** main[1 ] wherei**
1449+
1450+ ** [1 ] SimpleDebugging . foo3(SimpleDebugging . java: 16 ), pc = 15 **
1451+
1452+ ** [2 ] SimpleDebugging . foo2(SimpleDebugging . java: 10 ), pc = 8 **
1453+
1454+ ** [3 ] SimpleDebugging . foo1(SimpleDebugging . java: 6 ), pc = 8 **
1455+
1456+ ** [4 ] SimpleDebugging . main(SimpleDebugging . java: 19 ), pc = 10 **
1457+
1458+ ** wherei** 后的每一行代表一个方法调用和调用返回点(由程序计数器显示数值)。这里的调用序列是 ** main()** , ** foo1()** , ** foo2()** 和 ** foo3()** 。
1459+
1460+ 因为命令 ** list** 展示了执行停止的地方,所以你通常有足够的信息得知发生了什么并修复它。命令 ** help** 将会告诉你更多关于 ** jdb** 的用法,但是在花更多的时间学习它之前必须明白命令行调试器往往需要花费更多的精力得到结果。使用 ** jdb** 学习调试的基础部分,然后转而学习图形界面调试器。
1461+
1462+ ### 图形化调试器
1463+
1464+ 使用类似 JDB 的命令行调试器是不方便的。它需要显式的命令去查看变量的状态(** locals** , ** dump** ),列出源代码中的执行点(** list** ),查找系统中的线程(** threads** ),设置断点(** stop in** , ** stop at** )等等。使用图形化调试器只需要点击几下,不需要使用显式的命令就能使用这些特性,而且能查看被调试程序的最新细节。
1465+
1466+ 因此,尽管你可能一开始用 JDB 尝试调试,但是你将发现使用图形化调试器能更加高效、更快速地追踪 bug。IBM 的 Eclipse ,Oracle 的 NetBeans 和 JetBrains 的 IntelliJ 这些集成开发环境都含有面向 Java 语言的好用的图形化调试器。
1467+
13621468< ! -- Benchmarking -- >
13631469
13641470## 基准测试
13651471
1472+ > 我们应该忘掉微小的效率,说的就是这些 97 % 的时间:过早的优化是万恶之源。
1473+ >
1474+ > —— Donald Knuth
1475+
1476+ 如果你发现自己正在过早优化的滑坡上,你可能浪费了几个月的时间(如果你雄心勃勃的话)。通常,一个简单直接的编码方法就足够好了。如果你进行了不必要的优化,就会使你的代码变得无谓的复杂和难以理解。
1477+
1478+ 基准测试意味着对代码或算法片段进行计时看哪个跑得更快,与下一节的分析和优化截然相反,分析优化是观察整个程序,找到程序中最耗时的部分。
1479+
13661480< ! -- Profiling and Optimizing -- >
13671481
13681482## 分析和优化
0 commit comments