查找未处置的对象






4.93/5 (98投票s)
一个用于查找 .NET 应用程序中未释放对象的应用程序。
引言
谁愿意看到一个脏乱的房间?一个垃圾遍地的街道?一个发臭的厕所?
谁愿意看到托管代码中的对象未被释放?
希望没有人。
.NET 中非确定性的垃圾回收机制使得释放对象成为您的责任,如果不这样做,可能会发生糟糕的事情。除了终结的成本之外,如果应用程序以某种方式消耗资源而不触发垃圾回收,它可能会耗尽资源并崩溃。
但是,本文并非关于为什么您需要调用 Dispose
- 它假定您已经看到了光明。本文是关于查找未释放对象的方法。
技术
查找未释放对象的最简单方法是在每个可释放类型的终结器中添加一些日志记录。通过典型场景运行应用程序并检查日志应该可以告诉您哪些类型未被释放。如果您知道如何使用 Windbg 和 SOS,您还可以使用 !finalizequeue 扩展命令来查看所有可终结的类型。如果可终结类型的列表很小,您可以逐行检查代码,查找在这些类型实例上丢失的 Dispose
调用。
当代码库庞大,或者涉及第三方库时,这两种方法都不太有效。这时就轮到 Undisposed 了,这也是本文的主题。这是一个我编写的应用程序,它使用 CLR 性能分析 API 在运行时监视终结和对象创建,并向您显示所有终结的对象,以及相应的构造函数的堆栈跟踪,以帮助您快速定位有问题的代码部分。分析结果如下所示:

顶部面板显示了按类型终结的对象数量,底部面板显示了所选类型的构造函数的唯一堆栈跟踪。
如何使用它
下载该应用程序并使用以下命令注册 Undisposed.dll:
regsvr32 Undisposed.dll
该 DLL 需要 VS 2008 SP1 的 VC 可再发行组件包,如果注册失败,请从 此处 安装可再发行组件包。
启动 UndisposedViewer.exe

输入要启动的应用程序路径、命令行参数(如果有)以及日志文件位置。点击“自动查找可终结类型”以搜索应用程序中的所有可终结类型。您还可以手动输入类型名称。底部的文本框中的类型列表将是将被监视终结的类型。点击“Go”将启动目标应用程序。让应用程序经历各种操作,一旦退出应用程序,Undisposed LogViewer 将自动启动,向您显示在应用程序特定运行中未释放的对象。
工作原理
Undisposed.dll
该应用程序的关键组件是 Undisposed.dll - 一个与 CLR 性能分析 API 集成的 COM 组件。回到过去,CLR 依赖于普通的旧环境变量来知道加载哪个性能分析器。环境变量 "COR_ENABLE_PROFILING
" 告诉 CLR 性能分析已启用,它读取 "COR_PROFILER
" 环境变量以获取将作为性能分析器加载的组件的 CLSID。
CLR 性能分析 API 公开了一个 ICorProfilerCallback2
接口,该接口必须由 COM 组件实现。加载后,CLR 会使用该接口上的方法回调组件。那里有很多方法,但这些是 Undisposed 使用的方法:
Initialize
- 初始化所有跟踪器Shutdown
- 关闭所有跟踪器FinalizeableObjectQueued
- 通知终结跟踪器,后者将详细信息写入日志ObjectAllocated
- 通知对象跟踪器,后者将对象与通过遍历堆栈获得的堆栈跟踪进行映射SurvivingReferences
- 通知对象跟踪器关于在垃圾回收中幸存的对象MovedReferences
- 通知对象跟踪器关于在垃圾回收期间移动的对象
Initialize
回调还用于获取 ICorProfilerInfo
的接口指针。该接口上的 SetEventMask
方法用于告诉 CLR 性能分析器感兴趣的事件。对于 Undisposed,它们是:
hr = m_pCorProfilerInfo->SetEventMask(COR_PRF_MONITOR_CLASS_LOADS
| COR_PRF_MONITOR_OBJECT_ALLOCATED
| COR_PRF_MONITOR_GC
| COR_PRF_ENABLE_OBJECT_ALLOCATED
| COR_PRF_ENABLE_STACK_SNAPSHOT
)
COR_PRF_ENABLE_STACK_SNAPSHOT
允许性能分析器使用 ICorProfilerInfo2::DoStackSnapshot
遍历堆栈。这用于获取性能分析器正在跟踪的对象的构造函数堆栈跟踪。
还有许多其他有趣的实现细节,足以单独写一篇文章。但从高层次来看,代码的组织结构如下:

Controller
将 CLR 的调用转发给适当的跟踪器对象。ObjectTracker
维护所有被监视类型的所有活动对象的列表,以及它们的构造函数的堆栈跟踪。它还负责垃圾回收后的簿记工作。FinalizerRunLogger
在收到通知说一个被监视类型的对象即将被终结时,将对象类型和堆栈跟踪写出。TypeTracker
(图中未显示)维护内部数据结构到类型名称的映射。
UndisposedViewer.exe
主应用程序仅在设置好加载 Undisposed.dll 的环境变量后启动目标应用程序。它还将当前状态写入一个配置文件,然后由 COM 组件读取。这对于传递要监视的类型列表等信息是必需的。
UndisposedLogViewer.exe
日志查看器在目标应用程序退出后启动。其中没有太多逻辑,它只是读取 Undisposed.dll 生成的日志文件,并按类型和堆栈跟踪分组显示数据。
结论
编写此应用程序的灵感来自于 Lounge 中的这个帖子。希望它也能为您提供帮助。欢迎提供反馈和建议。
历史
- 2009-07-20 - 初始版本
- 2009-08-13 - 更新了源代码和二进制文件