65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ANTS Memory Profiler 剖析 .NET 应用程序的内存使用情况

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2009年7月31日

CPOL

8分钟阅读

viewsIcon

56447

内存剖析听起来令人望而生畏。但别再害怕了。Laila Lotfi 将为您详细讲解如何在不花费两天时间的情况下剖析 .NET 应用程序的内存使用情况。在此处阅读她的分步指南。

引言

.NET 中的自动内存管理使开发过程轻松了许多;但是,在应用程序中引入内存泄漏仍然很容易。例如,在复杂的应用程序中,很容易忘记取消注册事件处理程序,而这些事件处理程序又以占用您不再需要的对象内存而闻名。这通常会导致内存使用量增加,如果任其发展且未得到解决,可能会导致您的应用程序性能下降,甚至耗尽内存并崩溃。这时内存剖析器就显得必不可少。

基础知识

ANTS Memory Profiler 是一款用于 .NET 应用程序(包括 ASP.NET Web 应用程序)的内存剖析器,它可以帮助您定位内存泄漏、检查应用程序的内存使用情况并对代码执行健康检查。总之,ANTS Memory Profiler 将帮助您确定如何减少应用程序的内存占用。

让我们通过使用 ANTS Memory Profiler 5 来定位一个名为 QueryBee 的桌面应用程序中的内存泄漏来举例说明这一点。这是一个简单的 WinForms 应用程序,用于查询 SQL Server 数据库。它包含一个数据库连接对话框...

图 1:QueryBee – 数据库连接对话框。

...以及一个查询数据库的查询窗口。

图 2:QueryBee – 查询窗口。

我们知道我们的 QueryBee 应用程序存在内存泄漏,因为每次打开一个查询窗口然后又关闭它时,我们的内存使用量都会不断增加。

这是我们将要使用的剖析策略

  1. 等待 QueryBee 打开。
  2. 在不使用应用程序的情况下进行第一次快照 – 这个第一次快照将用作基线。
  3. 与 QueryBee 交互 – 连接到数据库,在查询窗体中输入 SQL 查询,执行查询并关闭窗体。
  4. 进行第二次快照。
  5. 检查剖析器在完成第二次快照的拍摄和分析后向我们显示的比较结果。

让我们开始吧。

打开 ANTS Memory Profiler 后,我们会看到一个设置对话框(图 3)。

图 3:ANTS Memory Profiler 5 设置对话框。

我们只需将其指向 QueryBee 并单击“开始剖析”即可。剖析器启动 QueryBee 并开始收集性能计数器数据(图 4)。

图 4:在剖析过程中,ANTS Memory Profiler 会收集性能计数器数据。剖析器现在正在剖析我们的应用程序。

此时,我们通过单击右上角的“拍摄内存快照”按钮来拍摄基线快照。剖析器强制进行完全垃圾回收,并拍摄其使用的堆内存快照,我们得到第一组结果(图 5)

图 5:第一次快照的结果 – 摘要屏幕。

现在,我们回到 QueryBee 并执行我们认为会导致内存泄漏的任务:我们从系统托盘中获取 QueryBee,选择一个数据库,在查询窗口中输入 SQL 查询,执行查询,并获得一些结果。

图 6:QueryBee – 结果显示在网格中。

既然我们已经获得了结果,我们就关闭窗体。

此时,窗口已经消失,我们期望内存使用量会回落到第一次快照时的水平,但事实并非如此。

图 7:尽管关闭了查询窗口,内存使用量仍在上升。

那么这里发生了什么?我们拍摄第二次快照并获得结果(参见图 8)。

(我们不打算拍摄任何进一步的快照,所以我们单击“停止剖析”按钮。)

图 8:摘要窗格比较了两次快照的结果。

我们切换到“类列表”以了解更多信息。“类列表”提供了快照中内容的更全面视图。

图 9:“类列表”允许您更详细地比较两个快照中的内存使用情况。

我们按“大小差异”排序,以查看自基线以来哪些内容的大小增加最多。

我们的类列表中有 816 个类,因此我们使用左侧的“筛选器”面板来减少列表中的类数量。因为我们拍摄了两个快照,一个非常有用的筛选器是“比较快照”筛选器。我们只想查看自基线以来创建的新对象,因此我们选择“仅新对象”筛选器。

图 10:我们现在应用了一个筛选器,仅显示自基线快照以来创建的新对象。

我们可以看到此筛选器对类列表上方条形图的影响 – 我们还剩 639 个类。我们已经移除了大约四分之一的类。这是一个不错的开始。

让我们通过应用另一个筛选器来进一步减少它。

我们知道我们在应用程序中多次调用 Dispose()。内存泄漏经常发生在即使调用了 Dispose(),对象也无法被垃圾回收,因为它仍然被另一个对象引用。因此,让我们应用“已处置但仍在内存中的对象”筛选器。

图 11:我们现在应用第二个筛选器,这极大地减少了我们剩余的类数量。

这极大地影响了剩余类的数量。向下扫描列表,我们还可以看到列表中有一个我们QueryForm 类的实例。

这不对。我们在查询完成后关闭了该窗体。为了找出它为什么仍然被保留在内存中,我们将查看这个 QueryForm 实例,方法是单击旁边的蓝色图标。

图 12:通过单击 QueryForm 旁边的蓝色图标,我们访问实例列表。

图 13:QueryForm 本身并不大,但“带子项的大小”列显示一个 QueryForm 实例占用了超过 80 MB 的内存。

我们发现 QueryForm 本身并不大,但“带子项的大小”列显示一个 QueryForm 实例占用了相当一部分内存。

所以,我们知道我们泄漏了 QueryForms,但我们需要找出原因。让我们通过单击 图标创建一个“对象保留图”。

图 14:此对象保留图显示了仍在引用 QueryForm 的对象。

此“对象保留图”显示了仍在引用我们 QueryForm 的对象。一旦我们弄清楚这一点,我们就可以回到代码中,打破将 QueryForm 保留在内存中的引用链。

有一个红色的提示告诉我们从底部开始,沿着图向上工作,直到找到需要断开的引用。我们只需要在一点断开链条,就可以让垃圾回收器清理掉它下面的所有东西。

首先,图告诉我们这个 System.EventHandler 引用了 QueryForm,如果我们再往上一级,它告诉我们这个事件处理程序被我们的 ConnectForm 实例引用——这是向我们索要数据库连接详细信息的窗体。换句话说,ConnectForm 通过事件处理程序保留了 QueryForm

如果我们更仔细地查看这个节点,我们会发现它实际上被 ConnectFormForegrounded 字段引用。

让我们在代码中找到这个 Foregrounded 事件。我们右键单击 QueryBee.ConnectForm 节点,然后在 Visual Studio™ 中打开 ConnectForm 源代码。

图 15:ConnectForm 源代码中的 Foregrounded 事件。

剖析器会自动跳转到 Foregrounded 事件。我们通过右键单击“查找所有引用”来检查它的使用情况。

图 16:Foregrounded 事件在三个地方使用。

我们有三个用法,我们发现最后一个用法是 QueryForm 注册 Foregrounded 事件的地方,但它似乎没有取消注册。如果我们修复了这一点,内存泄漏应该就会消失。

取消注册该事件的地方应该在 QueryFormDispose() 方法中。

图 17:QueryForm.cs 文件。

但是,由于 QueryFormConnectForm 没有引用,我们将不得不将其存储在一个成员字段中。

图 18:QueryForm.cs 文件。

现在我们可以修改 QueryForm.Designer.cs 文件中的 Dispose() 方法。

图 19:QueryForm.Designer.cs 文件:修改前的 Dispose()。

图 20:QueryForm.Designer.cs 文件:修改后的 Dispose()。

我们完成了,所以我们在 Visual Studio 中重新生成应用程序。

回到剖析器,我们启动一个新的剖析会话。我们想找出 QueryForm 的引用已经消失了。

请注意,它记住了我们上次的设置,所以我们只需要单击“开始剖析”。

图 21:设置对话框会记住上次的设置。

QueryBee 打开,我们拍摄第一个快照作为基线。

图 22:第一次快照的结果。

我们连接到一个数据库并执行一个 SQL 查询。

现在,我们将拍摄额外的快照,因为我们希望能够验证查询窗体是否已消失。这将为我们提供快照 2 的结果。

最后,我们关闭带有结果网格的查询窗口,然后拍摄第三个快照。

图 23:比较快照 2 和 3 的摘要屏幕。

我们切换到比较快照 1 和 3,使用时间轴下方的快照选择字段。

图 24:比较快照 1 和 3 的摘要屏幕。

让我们看看类列表中是否还有 QueryForm

图 25:时间轴显示内存突然下降。

没有,它已经消失了。我们不再泄漏窗体了。

正如您所见,跟踪一个被泄漏的窗体相当容易。

如果您想在自己的应用程序上尝试一下,可以 从 Red Gate 的网站下载为期 14 天的免费试用版

© . All rights reserved.