解冻你的应用
有很多调试器教程教你如何设置行断点、日志值或计算表达式。虽然仅这些知识就为调试应用程序提供了许多工具,但实际场景可能要复杂一些,需要更高级的方法。
在本文中,我们将了解如何在不了解项目结构和api的情况下定位代码,调试挂起的应用程序,并动态地修复错误代码。
这个问题
如果您想继续学习,请从克隆此存储库开始:
https://github.com/flounder4130/debugger-example
假设您有一个复杂的应用程序,在执行某些操作时挂起。您知道如何重现bug,但难点在于您不知道代码的哪一部分负责此功能。
在我们的示例应用程序中,当单击时发生挂起按钮N.然而,当你这样做时,要找到运行的代码并不容易:
让我们看看如何使用调试器来找到它。
方法断点
方法断点优于行断点的优点是它们可以用于类的整个层次结构。这在我们的案例中有什么用?
如果您查看示例项目,您将看到所有操作类都派生自行动
使用单一方法的接口:执行()
.
在此接口方法中设置方法断点将在调用派生方法之一时暂停应用程序。要设置方法断点,只需单击声明方法的行。
现在启动调试器会话并单击按钮N.应用程序被挂起ActionImpl14
.现在我们知道这就是对应于这个按钮的代码所在的位置。
虽然在本文中我们主要关注的是找到bug,但当您想要了解某个东西在大型代码库中如何工作时,这种技术也可以为您节省大量时间。
暂停应用程序
使用方法断点的方法工作得很好,但它是基于我们对父接口有所了解的假设。如果这种假设是错误的,或者由于其他原因我们不能使用这种方法,该怎么办?
我们甚至可以在没有断点的情况下完成。点击按钮N,当应用程序挂起时,转到IntelliJ IDEA。从主菜单中选择运行|调试操作|暂停程序.
应用程序将被挂起,我们可以检查调试器选项卡。这让我们了解应用程序当前在做什么。由于它是挂起的,我们可以识别挂起方法并追溯到调用地点。
与更传统的线程转储相比,这种方法有一些优点,稍后我们将介绍这一点。例如,它向您提供关于变量的信息,并反映应用程序的当前状态,允许您在任何时候恢复它。
线程转储
最后,我们可以使用线程转储,这不是严格意义上的调试器特性。它在运行应用程序时也可用。
点击按钮N,当应用程序挂起时,转到IntelliJ IDEA。从主菜单中选择运行|调试操作|获取线程转储.
扫描左边的可用线程,并在AWT-EventQueue您将看到问题的原因。
线程转储的缺点是,它们只提供程序状态的快照。不能使用线程转储来研究变量或控制程序的执行。
在我们的示例中,我们不需要求助于线程转储。但是,我们仍然想要提到这种技术,因为它可能在其他情况下有用,比如当您试图调试一个没有调试代理就启动的应用程序时。
理解这个问题
不管用的是什么方法,我们都得出结论ActionImpl14
.在这个类中,有人打算在一个单独的线程中执行工作,但是混淆了Thread.start ()
与Thread.run ()
,它在与调用代码相同的线程中运行代码。
IntelliJ IDEA的静态分析器甚至在设计时警告我们:
在UI线程上调用一个执行重提升(在本例中是重睡眠)的方法并阻塞它,直到它完成。这就是为什么我们在点击之后一段时间内无法在UI中做任何事情按钮N。
导致HotSwap
现在我们已经发现了bug的原因,让我们来修复这个问题。
我们可以停止程序,重新编译代码,然后重新运行它。然而,仅仅因为一个小更改就重新部署整个应用程序并不总是很方便。
让我们用聪明的方法来做。首先,纠正代码:
代码完成后,单击运行|调试操作|重新加载了类.一个气球出现了,确认新代码已经到达VM。
让我们回到应用程序并检查。点击按钮N不再挂起应用。
记住,HotSwap有它的限制.像DCEVM或JRebel这样的工具提供了扩展的HotSwap功能。如果你感兴趣,你可以多读一些。
总结
使用我们的推理和一些调试器特性,我们能够定位在项目中导致UI冻结的代码。我们还学到了暂停特性和线程转储在应用程序没有响应的情况下很有用。最后,我们修复了代码,没有在重新编译和重新部署上浪费任何时间,这在实际项目中可能很长。
在本文中,我们只触及了调试器如何在经典Hello World案例之外发挥作用的皮毛。如果您对与调试器或其他IntelliJ IDEA特性相关的任何特定主题感兴趣,请在评论中告诉我们,并随时阅读文档.
快乐发展,并继续关注更多!