教程:调试无响应的应用程序
最后修改时间:2023 年 9 月 21 日有很多调试器指南可以教您如何设置行断点、记录值或计算表达式。虽然这些知识本身就为您提供了许多用于调试应用程序的工具,但现实世界的场景可能会有些棘手,并且需要更高级的方法。
在本文中,我们将了解如何在不了解项目结构和 API 的情况下定位代码、调试挂起的应用程序以及即时修复错误代码。
问题
假设您有一个复杂的应用程序,当您执行某些操作时该应用程序会挂起。您知道如何重现错误,但困难在于您不知道代码的哪一部分负责此功能。
本教程的示例项目正是存在这个问题。让我们从克隆它开始:https ://github.com/flounder4130/debugger-example 。
运行应用程序
打开debugger-example
/src 。/main /java /App.java 在装订线中,单击 Run ,然后选择Run 'App.main()'。ShiftF10
该程序的用户界面有很多按钮。如果您尝试单击它们,您可能会发现当单击按钮 N时应用程序会挂起一段时间。
首先要做的就是找到处理该按钮事件的代码,但这可能并不那么容易:
让我们看看如何使用调试器来找到它。
方法断点
方法断点相对于行断点的优点是它们可以在类的整个层次结构上使用。这对于我们的例子有何用处?
如果您查看示例项目,您会发现所有操作类都是Action
通过单个方法从接口派生的:perform()
。每当调用派生方法之一时,在此接口方法中设置方法断点就会挂起应用程序。
设置方法断点
在
Action
界面中,单击方法声明处的装订线perform()
。
让我们运行调试会话,看看它会带我们去哪里。
在装订线中,单击 Run,然后选择Debug 'App.main()'。
在应用程序 UI 中,单击按钮 N。
该应用程序在 中被暂停ActionImpl14
。现在我们知道这个按钮对应的代码在哪里了。
提示
当您想要了解大型代码库中的某些内容如何工作时,此技术可以为您节省大量时间
暂停应用程序
使用方法断点的方法效果很好,但它基于我们对父接口有所了解的假设。如果这个假设是错误的,或者我们由于其他原因不能使用这种方法怎么办?好吧,我们甚至可以在没有断点的情况下做到这一点。
暂停
启动调试会话。
单击按钮 N,当应用程序挂起时,转到 IntelliJ IDEA。
从主菜单中,选择运行 | 调试操作| 暂停程序。
应用程序将被挂起,我们可以在调试工具窗口中检查线程的当前状态。这让我们了解应用程序目前正在做什么。既然是挂起,我们就可以识别挂起方法并追溯到调用地点。
与更传统的线程转储相比,此方法具有一些优点,我们将很快介绍。例如,它为您提供有关变量的信息并反映应用程序的当前状态,使您可以随时恢复它。
线程转储
最后,我们可以使用线程转储,严格来说这不是调试器功能。刚运行应用程序时它也可用。
获取线程转储
启动调试会话。
单击按钮 N,当应用程序挂起时,转到 IntelliJ IDEA。
从主菜单中,选择运行 | 调试操作| 获取线程转储。捕获的线程转储将在调试工具窗口中打开。
扫描左侧的可用线程,
AWT-EventQueue
您将看到导致问题的原因。
线程转储的缺点是它们仅提供生成时程序状态的快照。您不能使用线程转储来探索变量或控制程序的执行。
在我们的示例中,我们不需要诉诸线程转储。但是,我们仍然想提及此技术,因为它在其他情况下可能很有用,例如当您尝试调试在没有调试代理的情况下启动的应用程序时。
了解问题
无论使用哪种方法,我们都会得到ActionImpl14
。在此类中,有人打算在单独的线程中执行工作,但Thread.start()
与混淆Thread.run()
,后者在与调用代码相同的线程中运行代码。
IntelliJ IDEA 的静态分析器甚至在设计时警告我们:
在 UI 线程上调用执行繁重工作(或在本例中为大量睡眠)的方法,并阻止它直到完成。这就是为什么我们在单击Button N后一段时间内无法在 UI 中执行任何操作的原因。
热插拔
现在我们已经找到了错误的原因,让我们解决这个问题。
我们可以停止程序,重新编译代码,然后重新运行它。然而,仅仅因为一个小变化就重新部署整个应用程序并不总是很方便。
在运行时重新加载类
更正代码:
package actions; public class ActionImpl14 implements Action { @Override public void perform() { new Thread(() -> { try { // intense calculation Thread.sleep(15000); } catch (InterruptedException ignored) { } }).start(); } }
代码准备就绪后,单击运行 | 调试操作| 重新加载更改的类。出现一个气球,确认新代码已到达虚拟机。
返回应用程序并检查修复情况。单击按钮 N不应再挂起应用程序。
请记住,HotSwap 有其局限性。DCEVM 或 JRebel 等工具提供了扩展的 HotSwap 功能。如果您有兴趣,可以阅读有关它们的更多信息。
概括
使用我们的推理和一些调试器功能,我们能够找到导致项目中 UI 冻结的代码。我们还了解了暂停功能和线程转储在应用程序没有响应的情况下如何发挥作用。最后,我们修复了正在运行的应用程序,而没有浪费任何时间进行重新编译和重新部署,这在大型项目中可能会很长。
当然,我们使用了简化的项目来保持简洁,但是,相同的技术可以在现实项目中为您提供帮助,我们希望所获得的知识将在您的开发之旅中派上用场。
感谢您的反馈意见!