记一次 .NET 某手术室行为信息系统 内存泄露分析

一:背景

1. 讲故事

昨天有位朋友找到我,说他的程序内存存在泄露导致系统特别卡,大地址也开了,让我帮忙看一下怎么回事?今天上午看了下dump,感觉挺有意思,在我的分析之旅中此类问题也蛮少见,算是完善一下体系吧。

二:WinDbg 分析

1. 到底是哪里的泄露

.NET高级调试训练营中,我多次告诉学员们,在分析此类问题时一定要搞清楚是托管还是非托管的问题,否则就南辕北辙啦,接下来使用 !address -summary 观察下内存段。


0:000:x86> !address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unknown>                             17673          cd777000 (   3.210 GB)  83.76%   80.26%
Image                                  3087          1c14c000 ( 449.297 MB)  11.45%   10.97%
Free                                   1418           aae6000 ( 170.898 MB)            4.17%
Heap32                                   11           6ee0000 ( 110.875 MB)   2.82%    2.71%
Stack32                                 186           39c0000 (  57.750 MB)   1.47%    1.41%
Stack64                                 186            f80000 (  15.500 MB)   0.39%    0.38%
Other                                    24            1da000 (   1.852 MB)   0.05%    0.05%
Heap64                                    4            190000 (   1.562 MB)   0.04%    0.04%
TEB64                                    62             7c000 ( 496.000 kB)   0.01%    0.01%
TEB32                                    62             3e000 ( 248.000 kB)   0.01%    0.01%
Other32                                   1              1000 (   4.000 kB)   0.00%    0.00%
PEB64                                     1              1000 (   4.000 kB)   0.00%    0.00%
PEB32                                     1              1000 (   4.000 kB)   0.00%    0.00%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE                           16566          be8c2000 (   2.977 GB)  77.67%   74.43%
MEM_IMAGE                              4210          245be000 ( 581.742 MB)  14.82%   14.20%
MEM_MAPPED                              421          12403000 ( 292.012 MB)   7.44%    7.13%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT                            18901          e2904000 (   3.540 GB)  92.36%   88.50%
MEM_RESERVE                            2296          1297f000 ( 297.496 MB)   7.58%    7.26%
MEM_FREE                               1519           ad6d000 ( 173.426 MB)            4.23%

仔细观察卦中信息:可以看到总的提交内存 MEM_COMMIT = 3.5G 都被 <unknown>=3.2G 这块给吃掉了,这表示当前是一个赤裸裸的 非托管内存泄露 ,是某种代码通过 VirtualAlloc 这种方式直取 Windows内存管理器 内存,既然能用上 VirtualAlloc 肯定就不是业务程序员造成的,要想洞察 VirtualAlloc 的调用栈除了对程序安插监控和挂钩子,其余也没什么好办法了,仅仅从现有的 dump 中观察其实很难。

2. 真的没有希望吗

既然不知道是谁分配的,我们只能观察事发现场,或许能从内存现场中找到点答案,那怎么找事发现场呢?熟悉 Windows内存管理 的朋友都知道,要想分配内存,首先要在 虚拟地址 上分配一个内存段,然后将我们的数据放在内存段中的内存页上,所以思路就是找到所有 COMMIT 的 SEGMENT 即可,使用 !address -f:Unk,MEM_COMMIT 观察所有 Unk 的提交内存段。


0:000:x86> !address

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
79530000 79550000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
79550000 79570000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
79580000 795a0000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [NatK............]
795a0000 795c0000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [........d.......]
795c0000 795e0000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
79620000 79640000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
79640000 79660000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
79660000 79680000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
79690000 796b0000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
796c0000 796e0000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [..d.i.v...c.l.a.]
796e0000 79700000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [t...p.r.o.t.o.t.]
79700000 79720000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [t...p.r.o.t.o.t.]
79720000 79740000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [........8..z....]
79740000 79760000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
79760000 79780000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [........d.......]
79780000 797a0000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [.....OP...vy....]
797a0000 797c0000    20000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
797c0000 797c1000     1000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE                       <unknown>  [U...E.....H..E.P]
797c1000 797e0000    1f000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
797e0000 797e1000     1000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE                       <unknown>  [U...E.....H..E.P]
797e1000 79800000    1f000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [................]
...

卦中信息太多了,刷了好久才刷完,Break 之后观察内存段,发现 RgnSize = 20000 的段貌似要多一些,为了发现 0x20000 的size到底有多少,这里需要写一段脚本进行统计,截图如下:

记一次 .NET 某手术室行为信息系统 内存泄露分析

从卦中看 128k 的内存段个数就占用了 9000+,应该是什么东西分配了内存但没有合理释放。

由于没有给程序装监控,只能看内存段的地址上的内容了,这里使用 windbg 自带的 .writemem 命令将内存写到文件中观察,简单观察之后,发现里面有很多的 js 代码以及 html,比如下面的 PopupCalendar 方法,截图如下:

记一次 .NET 某手术室行为信息系统 内存泄露分析

3. 继续乘胜追击

既然抽到的几个内存段有这些 网页内容,但不见得大部分内存段都有这些内容,那怎么去验证呢?可以使用 s 搜索内存去求证一下,比如我全内存搜索包含 PopupCalendar 的内存段有多少。


0:000:x86> s-a 0 L?0xffffffff "PopupCalendar"
08a4ed7c  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 53 74 79  PopupCalendarSty
096f8fa3  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o
096f9026  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o
096ff5bb  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o
096ff63e  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o
...
f5fec996  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o
f5ff2f2b  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o
f5ff2fae  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o
f5ff9543  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o
f5ff95c6  50 6f 70 75 70 43 61 6c-65 6e 64 61 72 28 22 6f  PopupCalendar("o

记一次 .NET 某手术室行为信息系统 内存泄露分析

从内存地址看:PopupCalendar 从虚拟地址开头的 08a4ed7c 到虚拟地址快结束的 f5ff95c6 都遍布着这样的字符,而且高达 1532 处,这也就说明当前非托管内存中有大量的 html 页面被分配,但没有被释放,这也就是问题所在。

有了这些信息后接下来就找朋友反馈,为什么非托管内存中有这么多的 html 页面,是不是在 WPF 中不合理的使用了什么 浏览器引擎 ,分配之后未合理释放导致的泄露?

三:总结

这次事故应该是第三方组件在使用 html 的方式上造成的泄露,把包围圈缩小到这里相信朋友能很快的找到问题,验证问题。

PS:在 dump 中寻找非托管内存泄露其实很多时候都比较绝望,需要你对 Windows内存管理 有一个比较深入的理解,还需要一个不骄不躁的分析心态。

图片名称

原文链接:https://www.cnblogs.com/huangxincheng/p/17282019.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:记一次 .NET 某手术室行为信息系统 内存泄露分析 - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • Net core中使用System.Drawing对上传的图片流进行压缩(示例代码)

    在.NET Core中,使用System.Drawing对上传的图片流进行压缩是一项常见的任务,它可以帮助您减小图片的大小并提高应用程序的性能。在本攻略中,我们将详细讲解如何使用System.Drawing对上传的图片流进行压缩,并提供两个示例说明。 步骤一:安装NuGet包 要使用System.Drawing对上传的图片流进行压缩,您需要安装以下NuGet…

    C# 2023年5月17日
    00
  • unity 实现摄像机绕某点旋转一周

    Unity中实现摄像机绕某点旋转一周主要是通过设置摄像机的的位置和旋转角度来实现,在这里分享一下具体实现攻略。 使用transform.RotateAround旋转摄像机 在Unity中,transform组件具有一个RotateAround方法,可以用于将物体绕某个点旋转。因此,我们可以先通过旋转一个空物体作为中心点,然后使用RotateAround方法实…

    C# 2023年6月3日
    00
  • C# 基础入门–注释

    非常感谢你对C#基础学习的关注!注释是一种非常重要的编程元素,它能够加强代码的可读性、可维护性和可访问性。在本篇文章中,我将为您介绍如何在C#程序中使用注释,包括单行注释和多行注释。 单行注释 单行注释会在某一行的末尾添加标记符号“//”,表示该行后面的文字都是注释内容。例如,下面的代码演示了单行注释的使用: // 这是一个单行注释 int x = 5; /…

    C# 2023年6月7日
    00
  • C#基础之匿名方法实例教程

    C#基础之匿名方法实例教程 什么是匿名方法 匿名方法是一种没有名称且无需在代码顶部或其他位置定义的方法。这种方法可以在其声明和调用处定义,并且在其通常作为构造函数或方法参数的上下文中使用。在C#中,有三种类型的匿名方法,分别为Lambda表达式、委托化方法以及匿名方法定义语法。本篇文章将集中展示这三种不同的匿名方法的实例教程。 Lambda表达式 Lambd…

    C# 2023年6月6日
    00
  • C#实现读取指定盘符硬盘序列号的方法

    C#实现读取指定盘符硬盘序列号的方法可以分为以下几个步骤: 引入System.Management命名空间 要使用WMI类来获取硬盘序列号,需要引入System.Management命名空间。 using System.Management; 获取管理对象 可以通过ManagementObjectSearcher类来搜索计算机系统上可用的管理对象。通过查询W…

    C# 2023年6月8日
    00
  • 最全.NET Core 、.NET 5、.NET 6和.NET 7简介和区别

    .NET是一种用于构建多种应用的免费开源开发平台,可以使用多种语言,编辑器和库开发Web应用、Web API和微服务、云中的无服务器函数、云原生应用、移动应用、桌面应用、Windows WPF、Windows窗体、通用 Windows平台 (UWP)、游戏、物联网 (IoT)、机器学习、控制台应用、Windows服务。框架主要包括:.NET Framewor…

    C# 2023年4月18日
    00
  • ASP.NET Core – 缓存之分布式缓存

    分布式缓存是由多个应用服务器共享的缓存,通常作为访问它的应用服务器的外部服务进行维护。 分布式缓存可以提高 ASP.NET Core 应用的性能和可伸缩性,尤其是当应用由云服务或服务器场托管时。 与其他将缓存数据存储在单个应用服务器上的缓存方案相比,分布式缓存具有多个优势。 当分发缓存数据时,数据: 在多个服务器的请求之间保持一致(一致性)。 在进行服务器重…

    C# 2023年4月19日
    00
  • ItemsControl 数据绑定的两种方式

    我来为你讲解“ItemsControl 数据绑定的两种方式”的完整攻略。 一、介绍 在 WPF 中,我们通常使用 ItemsControl 来呈现一组数据集合。ItemsControl 提供了两种数据绑定的方式:通过 ItemsSource 属性绑定数据集合,或者通过数据模板绑定单个对象。 二、数据绑定方式一:ItemsSource 属性绑定数据集合 在此数…

    C# 2023年6月6日
    00
合作推广
合作推广
分享本页
返回顶部