托管内存转储分析器

马克·唐尼

对于日志中未显示的问题或无法通过本地调试进行调查的问题,您可能会尝试在问题在生产环境中处于活动状态时捕获诊断工件,如内存转储。然而,在与开发人员和支持工程师交谈时,我们知道内存分析可能很耗时、很复杂,需要多年才能完善的技能。

我们的新产品。NET分析器的开发有助于识别内存转储中可能指示生产服务出现问题的关键信号。这篇博客文章详细介绍了如何使用VisualStudio的新功能。NET内存转储分析器通过以下方式查找生产问题:

  • 打开内存转储
  • 根据转储选择和执行分析器
  • 审查分析仪的结果
  • 导航到有问题的代码

查找异步反模式

异步(Asynchronous)编程在上已经有好几年了。NET平台,但可能很难做好。关于异步的最佳实践以及如何正确使用它,这导致了一些反模式可能在您的服务处于高负载下时才会显示出来。

我们与一群支持工程师进行了交谈,“sync-over-async”反模式具有一组负面的性能特征:

  • 服务响应时间比正常情况慢。
  • 导致超时的请求数量增加。
  • CPU和内存往往保持在正常范围内。

当然,在这一点上,仍然有许多潜在原因可能是您有问题的服务行为的核心。我们的团队希望让开发人员更容易找到这些类型的问题,并确定您可以在哪里解决问题。

为了实现这个目标,我们已经开始开发一个新的.NET诊断分析器工具为了帮助刚开始转储调试的开发人员快速识别(或排除)影响生产环境中客户的问题,我们来看一个示例。

自动分析内存转储

假设该服务出现了我们前面提到的症状(超时、响应速度慢等),您如何验证(或失效)可能的根本原因?

打开内存转储

首先,让我们使用文件->打开->文件菜单并选择内存转储。您还可以将转储拖放到Visual Studio中以打开它。

请注意,内存转储摘要页面上有一个新的行动调用运行诊断分析.

Visual Studio转储操作摘要列表

选择此操作将启动调试器并打开新的诊断分析页面中包含可用分析仪选项的列表,按基本症状进行组织。

针对转储选择并执行分析器

在我的例子中,我担心我的“应用程序没有及时响应请求”.为了调查这些症状,我将选择下面的所有选项流程响应性因为这最符合我的应用程序的问题。

Visual Studio诊断分析窗口

点击分析按钮将启动调查,并根据内存转储中捕获的进程信息和CLR数据的组合显示结果。

审查分析仪的结果

在下面的图像中,分析器发现了两个错误结果,在选择第一个结果(“线程池超出线程数”)时,我可以看到分析摘要这意味着“CLR线程池正处于饥饿状态”。这一信息对调查非常重要,它表明CLR当前已使用了所有可用的线程池线程,这意味着在释放线程之前,我们无法响应任何新请求。

Visual Studio诊断分析结果

选择第二个结果“由于异步方法阻塞,线程池中的线程不足”,可以更明确地揭示问题的核心。通过查看转储,分析器能够找到我们无意中从异步线程上下文调用阻塞代码的特定位置,这直接导致线程池耗尽。

本例中的调用是“不要同步等待Monitors、Events、Task或任何其他可能会阻塞线程的对象。看看是否可以将方法更新为异步。”。我的下一个工作是找到有问题的代码。

Visual Studio诊断分析结果

通过单击显示调用堆栈链接Visual Studio将立即切换到显示此行为的线程调用堆栈window将向我展示所有需要进一步检查的方法。我可以快速区分我的代码(SyncOverAsyncExmple.*)和框架代码(System.*),并且可以将其作为我调查的起点。

Visual Studio调用堆栈

调用堆栈的每一帧(或行)都对应于一个方法,通过双击任何堆栈帧,我会提示VisualStudio引导我找到在此线程上直接导致此场景的代码。很遗憾,我没有与此应用程序关联的符号或代码,因此未加载符号下面的页面我可以选择反编译源代码选项.

Visual Studio反编译

在下面的反编译源代码中,我很清楚我有一个异步任务(ConsumeThreadPoolThread)调用包含WaitHandle(等待处理)。WaitOne方法,这将阻塞当前线程池线程,直到它收到信号(这正是我想要避免的!)。因此,为了提高我的应用程序的响应能力,我必须找到一种方法,从所有异步上下文中删除这些阻塞代码。

Visual Studio反编译代码

现在就去看看吧!

新的。NET内存分析器工具使开发人员和支持工程师更容易开始调试和诊断内存转储中的问题,使他们能够快速找出生产环境中的问题根源。

我们目前支持以下分析仪,并在不久的将来提供新的和改进的分析:

  • 终结器队列
  • CLR线程池
  • 异步同步
  • 死锁检测

我们相信还有更多的问题可以通过使用转储分析器快速确认,我们希望获得社区反馈,了解哪些问题对您最重要。

拜托帮助我们优先考虑要改进和构建的分析器填写这份调查!

9条评论

讨论结束。登录以编辑/删除现有评论。

  • 哈坎·福斯托克 0

    首先感谢你的精彩文章,
    你能详细说明一下如何打开内存转储吗?在这篇文章中,有一句话“首先,让我们在VisualStudio中打开内存转存”
    但我没有找到打开该面板的方法,我正在使用VS2019社区
    再次感谢您,先生。

    • 扬·克拉斯 0

      文件->打开->文件(Ctrl+O)

      如果要筛选,请选择文件类型“转储文件”,或查看相应的文件扩展名。

      双击dmp文件也可以。

      • 西蒙·拉巴尔 0

        这也是我的问题。谢谢

    • 马克·唐尼Microsoft员工 0

      如前所述,您可以执行文件->打开->文件并选择内存转储。
      如果转储没有扩展(托管Linux转储),可以使用打开-打开方式选项,然后选择托管Linux代码转储文件摘要.

      还可以将转储拖放到Visual Studio中以打开它。

  • 亚历山大·盖科 0

    嘿,当打开1GB完全转储时,我在Managed heap窗口中什么也没有得到。我做错什么了吗?

    • 马克·唐尼Microsoft员工 0

      嘿,亚历山大,
      打开1Gb内存转储应该没有问题,虽然内存可能耗尽,但阈值要高得多,您应该会收到有关它的消息,而不是空堆。你能打开反馈票证我可以让我们的工程师开始调查吗?

  • 朱利亚诺·贡卡尔维斯 0

    嘿,马克。我回答了调查,但忘了包括这个。

    如果有一个关于堆栈/堆栈帧大小的更详细的视图,这将非常有用。我们目前正在处理一些非常难调试的StackOverflowExceptions,并且在检查转储时VS中提供的信息非常不完善。

    一种显示每个线程的堆栈大小的方法,并且能够查看每个调用的堆栈帧大小,对于我们这方面的问题调查来说是完美的。今天获取这些信息的唯一方法是通过堆栈指针寄存器值,这非常不友好,迫使我们手动“计算”大小。

  • 马基·尼尔赫 0

    日志名称:Microsoft-Windows-Kernel-PnP/配置源:Microsoft-Windows-Kernel-PnP日期:2020-12-23 8:54:51 PM事件ID:442任务类别:无级别:警告关键字:用户:SYSTEM计算机:LAPTOP-R4Q8O097描述:设备USB\VID_04E8&PID_6860\R58M779M9JB由于部分匹配或不明确,未迁移。最后一个设备实例Id:SWD\WPDBUSENUM__USBSTOR#Disk&Ven_RIM&Prod_Disk&Rev_1.0#97ADD97173AB3CEBE0F5680285DBC758852E1558&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}类Guid:{eec5ad98-8080-425f-922a-dabf3de3f69a}位置路径:迁移等级:0xF000FFFFFFF122存在:错误状态:0xC0000719事件Xml:442 0 3 0 0 0x40000000000000 672 Microsoft-Windows-Kernel-PnP/Configuration LAPTOP-R4Q8O097 USB\VID_04E8&PID_6860\R58M779M9JB SWD\WPDBUSENUM__USBSTOR#Disk&Ven_RIM&Prod_Disk&Rev_1.0#97ADD97173AB3CEBE0F5680285DBC758852E1558&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}{eec5ad98-8080-425f-922a-dabf3de3f69a}0xf000fffff122 false 0xc0000719

    • 马基·尼尔赫 0

      这就是你如何找到旧内存转储。您可以了解数据是如何损坏的,以及为什么会损坏,这样我们就可以在数据损坏之前修复数据,以便重新使用它。

反馈usabilla图标