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在申请大对象时失败。
原文:https://blog.csdn.net/lazyleland/article/details/6704661
原文地址:https://www.cnblogs.com/cn2018/p/10655827.html