测量 Visual Studio 中的内存使用情况(C#、Visual Basic、C++、F#)

使用调试器集成的 内存使用情况 诊断工具进行调试时,查找内存泄漏和低效内存。 通过内存使用率工具可以拍摄一个或多个托管和本机内存堆的快照,帮助理解对象类型的内存使用率影响。 还可以在不附加调试器的情况下或通过面向正在运行的应用来分析内存使用情况。 有关详细信息,请参阅在发行版本或调试版本上运行分析工具。 有关根据需要选择最佳内存分析工具的信息,请参阅 选择内存分析工具

尽管可以随时在 内存使用情况 工具中收集内存快照,但可以使用 Visual Studio 调试器来控制应用程序在调查性能问题时的执行方式。 断点设置、步进、全部中断和其他调试器操作可以帮助将性能调查集中在最相关的代码路径上。 在应用运行时执行这些操作可以消除那些你不感兴趣的代码中的干扰,并且显著减少诊断问题所需的时间。

重要

Visual Studio 中的 .NET 开发(包括 ASP.NET、ASP.NET Core、本机/C++ 开发和混合模式(.NET 和本机)应用)支持集成了调试器的诊断工具。

在本教程中,你将:

  • 拍摄内存快照
  • 分析内存使用情况数据

如果 内存使用率 不提供所需的数据,性能探查器中的其他分析工具 提供可能有用的不同类型的信息。 在许多情况下,应用程序的性能瓶颈可能是由内存以外的内容(例如 CPU、呈现 UI 或网络请求时间)引起的。

备注

自定义分配器支持 原生内存分析器的工作原理是收集在程序运行时发出的分配 ETW 事件数据。 CRT 和 Windows SDK 中的分配器已在源级别上进行了注释,因此可以捕获其分配数据。 若要编写自己的分配器,对于返回的指针指向新分配的堆内存的任何函数,可使用 __declspec(allocator) 进行修饰,如以下 myMalloc 示例所示:

__declspec(allocator) void* myMalloc(size_t size)

收集内存使用情况数据

  1. 打开要在 Visual Studio 中调试的项目,并在要开始检查内存使用情况的点在应用中设置断点。

    如果你有一个怀疑内存问题的区域,请设置内存问题发生前的第一个断点。

    提示

    因为应用频繁分配和释放内存,因此捕获感兴趣的操作的内存占用情况可能很困难。为此,请在操作开始和结束时设置断点(或逐步执行操作),以查找内存改变的确切位置。

  2. 在要分析的函数或代码区域的末尾设置第二个断点(或在出现可疑内存问题后)。

  3. 诊断工具 窗口将自动显示,除非你已将其关闭。 若要再次显示该窗口,请依次单击“调试”>“Windows”>“显示诊断工具”

  4. 使用工具栏上的“选择工具”设置选择“内存使用率”

    诊断工具的屏幕截图。

    诊断工具的屏幕截图。

  5. 单击 调试/开始调试(或在工具栏上单击 启动,或按下 F5)。

    应用完成加载后,将显示诊断工具的“摘要”视图。

    “诊断工具摘要”选项卡的屏幕截图。

    备注

    由于收集内存数据可能会影响本机或混合模式应用的调试性能,因此默认禁用内存快照。 若要在本机或混合模式应用中启用快照,请启动调试会话(快捷键:F5)。 出现“诊断工具”窗口时,选择“内存使用情况”选项卡,然后选择 堆分析

    “启用快照”的屏幕截图。

    停止(快捷键:Shift+F5)并重启调试。

    “诊断工具摘要”选项卡的屏幕截图。

    备注

    由于收集内存数据可能会影响本机或混合模式应用的调试性能,因此默认禁用内存快照。 若要在本机或混合模式应用中启用快照,请启动调试会话(快捷键:F5)。 出现“诊断工具”窗口时,选择“内存使用情况”选项卡,然后选择 堆分析

    “启用快照”的屏幕截图。

    停止(快捷键:Shift+F5)并重启调试。

  6. 若要在调试会话开始时拍摄快照,请选择 内存使用情况 摘要工具栏上拍摄快照。 (在此处设置断点可能也会有所帮助。)

    “拍摄快照”按钮的屏幕截图。

    “拍摄快照”按钮的屏幕截图。

    提示

    若要创建内存比较基线,请考虑在调试会话开始时创建快照。

  7. 运行会触发第一个断点的方案。

  8. 当调试器在第一个断点暂停时,请选择 内存使用情况 摘要工具栏上拍摄快照

  9. 按 F5 将应用运行到第二个断点。

  10. 现在,拍摄另一个快照。

    此时,可以开始分析数据。

    如果在收集或显示数据时遇到问题,请参阅 排查分析错误并修复的问题。

分析内存使用情况数据

“内存使用情况摘要”表中的行会列出在调试会话期间拍摄的快照,并提供指向更详细视图的链接。

内存使用情况表的屏幕截图。

内存使用情况表的屏幕截图。

列名称取决于您在项目属性中选择的调试模式:.NET、本地或混合模式(同时包括 .NET 和本地)。

  • “对象(差异)”(.NET) 或“分配(差异)”(C++) 列显示拍摄快照时 .NET 或本机内存中的对象数

  • “堆大小(差异)”列显示 .NET 和本机堆中的字节数

拍摄了多个快照后,摘要表的单元格会包含行快照与前一个快照之间的值变化。

若要分析内存使用情况,请单击其中一个链接,打开内存使用情况的详细报告:

  • 若要查看当前快照与上一快照之间的差异的详细信息,请选择箭头左侧的更改链接(内存使用量增加)。 红色箭头表示内存使用量增加,绿色箭头表示减少。

提示

为了帮助更快地识别内存问题,差异报告按增加最多的对象类型进行排序,这些对象类型是在总体数量上增加最多的(单击 对象(差异) 列中的更改链接),或在总体堆大小上增加最多的(单击 堆大小(差异) 列中的更改链接)。

  • 若要查看仅所选快照的详细信息,请单击非更改链接。

    报表显示在单独的窗口中。

托管类型报告

选择内存使用情况摘要表中 对象(差异) 单元格的当前链接。

托管类型报表的屏幕截图。

备注

对于 .NET 代码,视图实例 图标(对象类型列中的实例图标)仅在使用 调试器集成的内存使用情况工具 或打开 堆快照 并选择 调试托管内存时可用。

顶部窗格会显示快照中类型的计数和大小,包括由类型引用的所有对象的大小(非独占大小)。

底部窗格中的“根的路径”树显示引用上部窗格中选择的类型的对象。 仅当引用对象的最后一个类型已发布时,.NET 垃圾回收器才会清理对象的内存。 有关使用“根的路径”树的详细信息,请参阅分析根的热路径

托管类型报表的屏幕截图。

顶部窗格会显示快照中类型的计数和大小,包括由类型引用的所有对象的大小(非独占大小)。

底部窗格中的“根的路径”树显示引用上部窗格中选择的类型的对象。 仅当引用对象的最后一个类型已发布时,.NET 垃圾回收器才会清理对象的内存。

“引用的类型”树显示上部窗格中选择的类型所持有的引用

引用对象报表的屏幕截图。

“引用的类型”树显示上部窗格中选择的类型所持有的引用

引用对象报表的屏幕截图。

若要在上窗格中显示所选类型的实例,请单击对象类型旁边的 视图实例 图标。

内存使用情况工具中实例视图的屏幕截图。内存使用情况工具中的

“实例”视图显示上部窗格的快照中选定对象的实例。 “根的路径”和“引用的对象”窗格显示引用所选实例的对象以及所选实例引用的类型。 当调试器在拍摄快照的点停止时,可将鼠标悬停在“值”单元格上方,从而在工具提示中显示对象的值

内存使用情况工具中实例视图的屏幕截图。

“实例”视图显示上部窗格的快照中选定对象的实例。 “根的路径”和“引用的对象”窗格显示引用所选实例的对象以及所选实例引用的类型。 当调试器在拍摄快照的点停止时,可将鼠标悬停在“值”单元格上方,从而在工具提示中显示对象的值

本机类型报告

在“诊断工具”窗口的内存使用情况摘要表中,选择“分配(差异)”或“堆大小(差异)”单元格的当前链接

本类型视图的屏幕截图。

本类型视图的屏幕截图。

类型视图 显示快照中类型的数量和大小。

  • 选择所选类型旁边的 视图实例 图标,以显示有关快照中所选类型对象的信息。

    实例 视图显示所选类型的每个实例。 选择实例可在“分配调用堆栈”窗格中显示导致创建该实例的调用堆栈。 (此信息仅在调试时可用。

    “实例”视图和“分配调用堆栈”窗格的屏幕截图。

  • 选择实例图标(所选类型的对象类型列中的实例图标)以显示有关快照中所选类型的对象的信息。

    实例 视图显示所选类型的每个实例。 选择实例可在“分配调用堆栈”窗格中显示导致创建该实例的调用堆栈

    “实例”视图和“分配调用堆栈”窗格的屏幕截图。

  • 选择 堆栈 以查看所选类型的分配堆栈。

    堆栈视图的屏幕截图。

  • 视图模式 列表中选择 堆栈视图,以查看所选类型的分配堆栈。

    堆栈视图的屏幕截图。

内存使用情况见解

对于托管内存,内存分析工具还提供多个功能强大的内置自动见解。 选择托管类型报表中的“见解” 选项卡,并显示适用的自动见解,例如 重复字符串稀疏数组,以及 事件处理程序泄漏

内存使用情况工具中见解视图的屏幕截图。

“重复字符串”部分显示了在堆上多次分配的字符串列表。 此外,本部分显示总浪费的内存,即字符串大小的(实例数 - 1) 倍。

稀疏数组 部分显示主要填充零个元素的数组,在性能和内存使用方面可能效率低下。 内存分析工具将自动检测这些数组,并显示由于这些零值而浪费多少内存。

Visual Studio 2022 版本 17.9 预览版 1 中提供的 事件处理程序泄漏 部分显示一个对象订阅另一个对象的事件时可能发生的潜在内存泄漏。 如果事件发布者的生存期超过了订阅者的生存期,即使没有对订阅者的其他引用,订阅者仍会保持活动状态。 这可能会导致内存泄漏,其中未使用的内存未正确释放,导致应用程序随着时间的推移使用越来越多的内存。

已知某些类型具有可以读取的字段,以确定它们所占用的原生内存的大小。 “见解”选项卡显示对象图中的假本机内存节点,这些节点由其父对象保留,以便 UI 能够识别它们并显示其大小和引用图

内存使用工具中的原生洞察视图的屏幕截图。

更改(差异)报告

  • 选择 诊断工具 窗口中 内存使用情况 选项卡摘要表中的更改链接。

    选择单元格中的更改链接的屏幕截图。

    选择单元格中的更改链接的屏幕截图。

  • 在托管或本机报告的“比较对象”列表中选择快照

    从“比较对象”列表中选择快照的屏幕截图。

    从“比较对象”列表中选择快照的屏幕截图。

更改报告会向基本报告添加一些列(标记为“(差异)”),显示基本快照值与比较快照之间的差异。 下面是本机类型视图差异报告可能会采用的外观:

本机类型差异视图的屏幕截图。

本机类型差异视图的屏幕截图。

顶部窗格会显示快照中类型的计数和大小,包括由类型引用的所有对象的大小(非独占大小)。

博客和视频

在调试过程中分析 CPU 和内存

Visual C++ 博客:Visual C++ 2015 中的内存分析

后续步骤

本教程介绍了如何收集和分析内存使用情况数据。 如果您已经完成了性能分析器的 教程,您可能想要了解使用性能分析工具优化代码的一般方法。

在本教程中,你已了解如何在调试时收集和分析内存使用情况数据。 你可能想要详细了解如何使用性能探查器分析发布版本中的内存使用情况。