32位操作系统的寻址空间是4G,其中有2G被操作系统占用,也就是说留给用户进程的内存只有2G(其中还要扣除程序加载时映像占用的部分空间,一般只有1.6G~1.8G左右可以使用)。

如果进程运行中需要申请内存,而操作系统无法为其分配内存空间,则会产生内存不足的异常,在.net中为System.OutOfMemoryException(The exception that is thrown when there is not enough memory tocontinue the execution of a program.)。

虽然最终的表现都为OutOfMemoryException,但其产生的原因可能是不一样的,动手解决此问题之前需要先对进程当前内存的使用状态进行分析,找出正确的原因,才能对症下药。下面分享一下调试此类问题的一些心得。

一、使用Perfmon.exe

1)   命令行输入perfmon.exe。打开“性能”。

2)   在“性能日志与警报-计数器日志”上右键,选择“新建日志设置”。

3)   输入日志名称,如“OOM”。

4)   在“常规-计数器”中删除所有默认的计数器(如果有)。

5)   点击“添加计数器”,性能对象选择“.NET CLR Memory”,计数器选择并添加“Bytes in all heaps”、“Large Object Heap Size”。同样“性能对象”选择“Process”,计数器选择并添加“Virtual bytes”、”Private bytes”。注意点击“添加”前需要在“从列表选择范例”选择框选择需要监控的进程。

另外,如果当前系统登陆的用例对目标进程没有调试权限,需要在“运行方式”框里填入domain\username,并输入密码。

6)   数据采样间隔可以设置小一点,如1秒钟。

7)   点击“确定“,新的计数器日志就新建成功了。右边的框框中可以看到新的计数器,绿色表示正在运行中。”“日志文件名“列显示了本次监控结果将写入的日志文件名(同一个计数器运行多次,写入的日志文件名是不同的)。

8)   让程序与计数器运行一段时间,然后停止计数器(为什么要停止计数器?我的机器上测试的时候,需要先停止计数器后,才会把监控的结果写到日志文件中,如果不先停止,在下面的监视器中将看不到计数器运行这段时间的监控结果。)。

9)   点击“系统监视器“。点击”“查看日志数据”(图标为)按钮,在“来源”选项卡里添加日志文件为刚刚我们新建的计数器产生的日志文件。下方可选择时间范围,这里选全部即可。然后在“数据”选项卡里添加需要查看的计数器(此选择卡还可以定义不同的计数器显示的样式及显示比例)。

10) 从图上可以看到在计数器运行的时间段中,被监控进程的内存使用情况。在添加计数器的窗口中有对相应计数器的简单说明,下面是几个常用的计数器:

·           Bytes in all Heaps:.net托管堆(GC)使用的总内存。包括0代、1代、2代及大对象堆。

·           Large Object Heap size:大对象堆使用的内存。.net在分配内存时大于85K的对象会被放到这个堆中,不同于0、1、2代,大对象堆中的内存不是连续的,在垃圾回收时也不会移动大对象的地址(我系统显示为大于20K对象为大对象,实际上2.0应该为大于85K)。

·           Private bytes:该计数器记录了当前通过VirtualAlloc API Commit的Memory数量。无论是直接调用API申请的内存,被Heap Manager申请的内存,或者是CLR 的managed heap,都算在里面。跟Handle Count一样,如果在整个程序周期内总体趋势是连续向上,说明有MemoryLeak(摘自百度)。

·           Virtual bytes:该计数器记录了当前进程申请成功的用户态总内存地址,包括DLL/EXE占用的地址和通过VirtualAlloc API Reserve的Memory Space数量,所以该计数器应该总大于Private Bytes。一般来说,Virtual Bytes跟Private Bytes的变化大致一致。由于内存分片的存在, Virtual Bytes跟Private Byes一般保持一个相对稳定的比例关系。当Virtual Bytes跟Private Bytes的比例关系大于2的时候,程序往往有比较严重的内存地址分片(摘自百度,但对.net程序来说一般差别在200M以下还算是正常的)。

11) 有了上面几个计数器的结果之后,一般可以通过以下规则大致定位问题的所在:

·           Virtual bytes增长但Private bytes没有显著增长。为Virtual bytes泄露。

·           Private bytes增长但bytes in all heaps没有显著增长。为非托管资源泄露,检查有没有COM组件或其它非托管调用没有正确释放内存。

·           Bytes in all heaps显著增长。为.net托管内存泄露。由于.net内存是GC管理的,自动回收,这里有可能是缓存了过多的数据,或程序中引用混乱导致本来需要被回收的数据还被其它对象所引用从而GC没法回收这部分数据。

·           Bytes in all heaps有增长但使用不多,系统剩余可用内存也比较多(需要再添加相应的计数器)。这种情况比较少见,但我遇到过一次是由于非托管在存在大量碎片,导致.net在申请大对象时失败。

二、使用Windbg

如果是由于.net托管内存导致的内存泄露,可以用Windbg进一步排查问题(非托管的也可以,但还没有对这方面进行详细研究过:))。

1)   加载SOS.dll。

.loadC:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll

2)   保存进程的映像文件。

.dump /ma “c:\oom.dmp”

3)   查看内存的使用情况。

!address –summary

RegionUsageIsVAD:VirtualAlloc的内存,一般为GC占用。

RegionUsageFree:可用内存。

RegionUsageImage:加载dll或exe占用的内存。

RegionUsageStack:线程堆栈占用的内存(.net中如果一个递归函数有问题导致无限循环调用会产生StackOverflowException)。

其它的可以参考Windbg文档,或打!address -?获得命令说明。另外上面有一个重要的信息,即Largest free gegion,我这里关心其size为18280KB,即是说当前可申请的最大连续内存块为18M多,也意味着如果此时进程去申请大于此数值的内存,也会报OutOfMemory(尽管目前Free的内存总共还有400多M,打!address –RegionUsageFree可以看到这400多M的内存的分块情况),通常引起此问题的原因,可能是非托管调用引起的严重内存碎片,因为托管的内存是连续的。由于大对象申请失败的问题调试,后面还会再进一步详细说明。

4)   查看托管堆内存的使用情况。

!eeheap –gc

上面显示了GC各个代及大对象堆的大小及每个段(segment)的大小、地址范围等等信息。GC在分配内存的时候是按段申请,按段释放的,也就意味着,GC占用的内存要比你的程序中为对象实际申请的总内存要大一点,如果程序为对象申请一块内存,而当前段的最大可用内存不足以分配时,GC为向系统申请新的段,从上面看到段的大小为16M左右,应该是按某种算法得出新段的大小(比如当前可用内存,操作系统或.net framework的版本等,只是我的猜测,有兴趣的童鞋自己查查文档后告诉我:))。

5)   查看当前托管堆中的对象,及每种对象占用的内存大小。

!dumpheap –stat

从上图中看到最大的类型为字符串,共占用了135M内存。

6)   查看某种类型的所有实例地址。

!dumpheap -mt 793308ec

7)   查看某个对象的信息。

!do [对象地址]

对象地址可以在!dumpheap –mt命令的第一列中得到。

8)   查看某个对象占用的内存大小。

!objsize [对象地址]

如果对象引用了其它对象,此命令会把其引用的其它对象占用的内存也算进去。

9)   查看数组中的元素。

!dumparray [数组对象地址]

如果用!do得到的对象为数组,用此命令得到数组中每个元素的地址,再用!do打出数组元素的信息。

10) 查看对象与其它对象的引用情况。

!gcroot [对象地址]

这个命令在判断.net托管内存泄露很有用,它可以得到某个对象没有被GC释放掉的原因(因为存在根对象的引用关系)。

11) 查看对象大小大于某个数值的所有对象。

!dumpheap –min 10000000

12) 查看大对象堆的对象。

!dumpheap –startatlowerbound [大对象堆的起始地址]

大对象堆的起始地址可以由命令!eeheap –gc得到。

13) 调试由大对象内存分配不足引起的OutOfMemory。

有些情况下,明明内存还剩下很多,但是由于非托管带来的内存碎片,导致连接内存不足以分配程序申请的大对象的内存,这时也会报内存不足的异常。要确定内存不足是否由此原因引起的可按以下步骤调试:

在程序申请大对象的时候,用windbg打一个断点,并把大对象申请的内存的大小打印出来。

0:027>x mscorwks!WKS*allocate_large*

79f7d9ebmscorwks!WKS::gc_heap::allocate_large_object = <no type information>

0:027>bp 79f7d9eb "?@ebx;!clrstack"

如果程序申请大对象,会有类似下面的输出,大对象的大小为52M。

Evaluateexpression: 52679596 = 0323d3ac

此时可以在输出里看到堆栈,确定是程序哪个代码需要申请这么大的对象,是否属于正常。也可以用!address –summary看当前可申请的最大连接内存块大小,如果小于待申请的大对象大小,则会出现内存不足。

另外,在以前的调试中我得到这么一个结论,如果程序声明了一个长度大于85000/4=21000的数组,这时数组实际占用的内存大于85K,GC会把这个数组放在大对象堆中,对于List类型,其长度是可以动态增加的,如果长度从小于21000到达到21000,GC也会把它移到到大对象中(刚一开始长度小于21000时不在大对象堆中)。

14) 查看GC的终结队列及线程。

另一种导致托管内存没有被释放的原因(除了对象被引用)就是GC的终结线程被阻塞了,从而导致可以释放的对象来不及被释放。可以按以下步骤调试此类问题:

!finalizequeue

有类似以下的输出:

这里需要关注的是Ready for finalization XX objexts,表示终结队列中有多少个对象正在等待被回收,如果数量比较大,可以进一步看看终结线程的堆栈。

!threads

后面带有Finalizer的即终结线程。

~[线程号]s

!clrstack

根据堆栈信息,可以初步断定引起终结线程阻塞的代码位置,然后针对代码做进一步的分析。

转自:http://blog.csdn.net/lazyleland/article/details/6704661

内存不足(OutOfMemory)的调试分析的更多相关文章

  1. 使用Windbg和SoS扩展调试分析.NET程序

    在博客堂的不是我舍不得 - High CPU in GC(都是+=惹的祸,为啥不用StringBuilder呢?). 不是我舍不得 - .NET里面的Out Of Memory 看到很多人在问如何分析 ...

  2. JVM内存状况查看方法和分析工具

    Java本身提供了多种丰富的方法和工具来帮助开发人员查看和分析GC及JVM内存的状况,同时开源界和商业界也有一些工具可用于查看.分析GC及JVM内存的状况.通过这些分析,可以排查程序中内存泄露的问题及 ...

  3. CVE-2012-0003:Microsoft Windows Media Player winmm.dll MIDI 文件堆溢出漏洞调试分析

    0x01 蜘蛛漏洞攻击包 前言:2012 年 2月,地下黑产中流行着一款国产名为蜘蛛漏洞的攻击包 -- "Zhi-Zhu Exploit Pack",该工具包含 5 个漏洞,都是在 ...

  4. CVE-2010-3974:Windows 传真封面编辑器 FxsCover.exe 双重释放漏洞调试分析

    0x01 堆空间申请后的双重释放 Windows FxsCover 程序存储封面编辑器的信息,封面编辑器是传真服务的一个组件,通过解析特定的传真封面文件(.cov)时,会调用类析构函数对同一内存中的栈 ...

  5. CVE-2017-11882:Microsoft office 公式编辑器 font name 字段栈溢出通杀漏洞调试分析

    \x01 漏洞简介 在 2017 年 11 月微软的例行系统补丁发布中,修复了一个 Office 远程代码执行漏洞(缓冲区溢出),编号为 CVE-2017-11882,又称为 "噩梦公式&q ...

  6. CVE-2018-0802:Microsoft office 公式编辑器 font name 字段二次溢出漏洞调试分析

    \x01 前言 CVE-2018-0802 是继 CVE-2017-11882 发现的又一个关于 font name 字段的溢出漏洞,又称之为 "第二代噩梦公式",巧合的是两个漏洞 ...

  7. 大并发连接的oracle在Linux下内存不足的问题的分析

    大并发连接的oracle在Linux下内存不足的问题的分析 2010-01-28 20:06:21 分类: Oracle 最近一台装有Rhel5.3的40G内存的机器上有一个oracle数据库,数据库 ...

  8. DEBUG模式下, 内存中的变量地址分析

    测试函数的模板实现 /// @file my_template.h /// @brief 测试数据类型用的模板实现 #ifndef MY_TEMPLATE_H_2016_0123_1226 #defi ...

  9. Linux调试分析诊断利器——strace

    strace是个功能强大的Linux调试分析诊断工具,可用于跟踪程序执行时进程系统调用(system call)和所接收的信号,尤其是针对源码不可读或源码无法再编译的程序. 在Linux系统中,用户程 ...

随机推荐

  1. android学习视频(实战项目演练)

    1.基于Android平台实战无线点餐系统(客户端(Client)和服务端(Server))①http://kuai.xunlei.com/d/xmBrDwI8CAAyXVFRa3d②http://k ...

  2. java 调用 .net webservice

    1.首先下载Axis2工具包 2.解压之后用cmd命令进入bin目录WSDL2Java.bat -uri http://192.168.20.42:9999/LoginService.asmx?wsd ...

  3. 小结JS中的OOP(下)

    关于JS中OOP的具体实现,许多大神级的JS专家都给出了自己的方案. 一:Douglas Crockford 1.1 Douglas Crockford实现的类继承 /** * 原文地址:http:/ ...

  4. 插件二之页面加载进度条pace.js

    关于pace.js pace.js包含14样式,每种样式可以自定义颜色,官方下载中提供了几种颜色的主题,使用方式也很简单,引入pace的js文件跟所需样式文件即可 <link rel=" ...

  5. js获取浏览器高度和宽度值,尽量的考虑了多浏览器。

    js获取浏览器高度和宽度值,尽量的考虑了多浏览器. IE中: document.body.clientWidth ==> BODY对象宽度 document.body.clientHeight ...

  6. 【LeetCode】169 - Majority Element

    Given an array of size n, find the majority element. The majority element is the element that appear ...

  7. 千万别把js的正则表达式方法和字符串方法搞混淆了

    我们在字符串操作过程中肯定经常用了test() split() replace() match() indexof()等方法,很多人经常把用法写错了,包括我,所以今天细细的整理了下. test()是判 ...

  8. PHP 碎片

    1. $_SERVER['REMOTE_ADDR'] cannot be modified by the user or via HTTP so you CAN trust it. -- 用这个可以有 ...

  9. Hadoop 中疑问解析

    Hadoop 中疑问解析 FAQ问题剖析 一.HDFS 文件备份与数据安全性分析1 HDFS 原理分析1.1 Hdfs master/slave模型 hdfs采用的是master/slave模型,一个 ...

  10. http协议要点

    概念: HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写.它的发展是万维网协会(World Wide Web Consortium)和Internet工作小组 ...