dotnet产品调式-内存调试二
情景:内存耗费
现在我们都了解了dotnet内存管理和垃圾收集的基本原理,让我看看asp.net应用程序是如何使用的。这些情景向我展示了如何去调式内存消耗的问题。你们也许已经知道,内存泄露并不是动态重新分配内存而导致的,重新动态分配内存不会导致内存泄露。一个很小的内存泄露可能不会被注意到,也可能仅导致一个很小的危害,但是大的内存泄露会通过消耗可用内存来导致严重行能问题。另外,还有一个内存有关的问题,它不是 “真的”内存泄露,但能体现出内存泄露的征兆。
有一些典型的客户情景可能暗示内存问题:
情景1 :一个电子商务网站出售产品,浏览的客户抱怨数据丢失,看见 “Server Application Unavailable”错误。他们不得不重新登陆,并不知道这是为什么。额外的,服务端性能 下降当内存使用增加的时候。
情景2 :一个网站提供视频上传得服务,当有一个用户上传大文件时,服务器进程被回收,文件上传失败。
处理这里问题的一个思路:
当我们调试内存相关的问题时,我们有很多方法,下面的图展示了一个思路:
流程图(处理内存耗费的问题)
流程已经展示,现在让我们看看细节吧
错误是指向内存泄露的征兆么?
如果你使用iis,当查看应用程序事件日志,发现一个asp进程被回收,你看到这样一段类似的信息aspnet_wp.exe(PID:1234) 被回收,因为内存消耗超过~MB(~代表可用内存的百分比)
注 在iis6中对应的进程是 w3wp.exe
我的内存去哪里了?
下一步是找到那一个进程使用了太多的内存。工具有:性能监视器 或者 任务管理器 能够指出这些进程。一旦你知道哪个进程有很大的嫌疑。你可以运行AutoDump+ (ADPlus)来创建一个w3wp.exe进程的内存dump文件,然后用WindDbg 调试工具和 SOS.dll 调试扩展来检查托管和本地内存。这个随后讨论。
本地内存耗费
当使用性能监视器,如果 “Private Bytes ”计数器和“ .NET # of Bytes in all Heaps ”计数器以相同的比率在增加。这是一个证明是托管内存消耗的证据。看看已经分配内存的大小,思考GC正在干什么。当查看dump文件的时候,使用SOS命令来列出托管堆的大小,和这个dump 文件的大小比较一下,看看什么细节你能学到从有关大对象堆(LOH)和代(Generations)的知识中。查看一下是不是大对象(85K或更大)或小对象消耗了大部分的内存。如果是前者,查看有关LOH的一些细节,如果是后者,考虑第0代(G0)、第1代(G1)、第2代(G2)中包含什么。对于大对象,看看是否被根化(rooted),是否需要根化(rooted),如果他们没有被根化,他们就是下一个被回收的目标,确认最终它们是不是被合理的回收了。
使用WinDbg 和SOS.dll 来查看所有这些很多的小对象的细节是比价困难的。在这种情况下,使用 CLR Profiler 这个工具来查看细节会比较方便。这个工具查看大对象也是可以的。
本章节将使用这些工具。
内过消耗过程
下面的一个场景是经过简化的,最真实的一个描述。一个ASP.NET应用程序分配了太多的内存并且被回收了,因为它超过了在进程模型中的内存限制。下面的内容是阐明这种调试技术可以帮助你来定位一个问题,生产应用的环境可能不像这里这样简单清晰,但是你可以使用类似的这种技术来定位内存消耗的问题。这个例子中申请了一个大内存,通过消耗可用内存足以导致严重的性能问题,这个例子中asp.net 页面申请了大对象(20M每次),然后把它们缓存在asp.net 的cache中。缺省情况下,当内存消耗达到60%,asp.net进程就会被回收,下面讲述的内容的目的是找出原因。
首先,你可以按如下步骤来做:
2) 按照上面的流程图中的步骤来找到错误,你可以从页面反馈的信息和应用程序事件查看器中去找。
3) 创建一个dump文件,然后寻找里面的一些数据信息,工具可以使用WinDbg 和SOS.dll.
4) 检查针对这个asp.net 进程的托管内存,并找到是什么对象消耗了大部分的内存。
5) 当你从dump文件中找到一些证据和蛛丝马迹后,去查看源代码。
以上的步骤可以锻炼你的查找定位能力。
下面请跟着我的思路
预备环境
首先,收集一些初始的数据,工程文件、安装程序都可以下载。
表格中几个字段的意义是:
|
字段
|
含义
|
|
StartTime
|
Aspnet_wp.exe/w3wp.exe 进程开始的时间
|
|
Age
|
该进程运行的总时间长度
|
|
ProcessID
|
该进程的id编号
|
|
RequestCount
|
完成的请求的数目,初始化是0
|
|
Status
|
该进程的当前状态,
Alive:进程正在运行。
Shutting Down:进程正在关闭
ShutDown:进程已经关闭,正常关闭
Terminated:异常关闭,
|
|
ShutdownReason
|
进程关闭的袁鹰:
Unexpected: 意外关闭,非希望的关系。
Requests Limit:进程执行该请求超出了限制
Request Queue Limit:处理该请求的进程超出了队列数目的限制
TimeOut:进程重新启动,因为超时。
Memory Limit Exceeded:进程超过内存限制
|
|
PeakMemoryUsed
|
最大内存使用量,这个值对应性能监视器中的Private Bytes (maximum amount) 计数器
|
你应该已经研究过源代码了。
每次,点击 “Allocate 20M Objects”按钮,五个缓存对象(缓存key 也是唯一的)被放到System.Web.Cache中,“Allocate 200K Objects”按钮的工作方式也是差不多,但它每次是创建200 k 的对象,这个按钮的目的是在试验中模拟非致命的泄漏,可以和20M 的交换测试。
“Free Memory”按钮在当前会话范围内有一个缓存key的列表,能够清楚缓存,它调用System.GC.Collect() 方法来强制垃圾收集。这里需要注意,System.GC.Collect() 方法仅仅是为了示范的目的,并不是推荐我们在实际中这样使用,显式的调用System.GC.Collect() 会改变GC自动收集的特性,不断地调用System.GC.Collect() 会刮起所有的线程直道垃圾收集结束,这会大大降低性能。
“Refresh Stats”按钮可以刷新内存统计数据。
你可以使用任务管理器和性能监视器来查看到更多的有关信息。
打开任务管理器,在“进程”选项卡上,点击“查看”菜单,点击“选择列”然后把“虚拟内促页大小(Virtual Memory Size)”这个给勾上。如果你是在服务器上,远程的控制,你要把 “显示所有用户进程(Show processes from all users)”这个也勾上。
读出这个虚拟内存的数据,这个值是9,820 KB,还是比较合理的。
|
Process
|
Virtual memory size
|
|
Aspnet_wp.exe
|
9,820 KB
|
打开性能监视器。
1)在“控制面板”中,打开“管理工具(Administrative Tools)”,双击 “性能(Performance)”
在性能管理器工具栏上 点击“+”打开“添加计数器”对话框,从“性能对象(Performance object)”的下拉框中选择“.NET CLR Memory”。
2)在“从列表选择实例”中选中“w3wp.exe”。
3)把下图中所列出的计数器全部选上。重复上面的步骤把“ASP.NET Applications”和“Process”两个性能对象也添加进来,计数器也添加进来。
4)点击工具栏上的“查看报告”按钮,从图表转换到文本模式
在监视器中特别关注 # Bytes in all Heaps, Large Object Heap Size, 和 Cache API Entries.
# Bytes in all Heaps 代表这 Gen 0 Heap Size, Gen 1 Heap Size, Gen 2 Heap Size, 和 Large Object Heap Size的大小的总和,指示当前内存中分配的GC堆的大小,以 字节为单位。
Large Object Heap Size 代表大对象堆的大小,以字节为单位,该计数器在垃圾收集完后才会改变计数,并不是每次分配都会刷新计数。
Cache API Entries:代表在应用程序缓存(cache)中的实体的总个数。
当前大小是:
Large Object Heap Size: 22,608 bytes
# Bytes in all Heaps: 2,589,252
Cache API Entries: 0
有关计数器的更多信息可以查看
".NET Framework General Reference: Memory Performance Counters" on the MSDN Web site at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/gngrfmemoryperformancecounters.asp.
".NET Framework Developer's Guide: Performance Counters for ASP.NET" on the MSDN Web site at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconperformancecountersforaspnet.asp.
第一次分配过程
现在你已经有了应用程序、性能监视器的原始数据,看看本地和托管的内存消耗吧,点击“Allocate 20 MB Objects”按钮一次,你得到的数据将和下面表格中的类似:
|
Field
|
Baseline value
|
New value
|
|
StartTime
|
07/08/2002 12:14:06 PM
|
07/08/2002 12:40:59 PM
|
|
Age
|
00:00:04.2961776
|
00:00:33:9585520
|
|
ProcessID
|
3,212
|
3,212
|
|
RequestCount
|
0
|
1
|
|
Status
|
Alive
|
Alive
|
|
ShutdownReason
|
N/A
|
N/A
|
|
PeakMemory Used
|
5,948
|
8,904
|
|
Updated Memory Stats
|
Baseline value
|
New value
|
|
GC.TotalMemory
|
780 KB
|
100,912 KB
|
|
Private Bytes
|
8,896 KB
|
117,616 KB
|
下面出现了一个新的表格:
GC.TotalMemory:表示总的托管内存数目。该值对应性能监视器中的# Bytes in all Heaps(该计数器 位于 .NET CLR Memory Performanc对象中)e
Private Bytes 是总的进程本地内存数目,它是不能与其他进程共享的内存。
你可以看到,RequestCount 变成了1 ,GC.TotalMemory 和 Private Bytes 的值几乎都是100M。
看看源代码:
for (int i = 0; i< 5; i++)
{
long objSize = 20000000;
string stime = DateTime.Now.Millisecond.ToString();
string cachekey = "LOCache-" + i.ToString() + ":" + stime;
Cache[cachekey] = CreateLargeObject(objSize);
StoreCacheListInSession(cachekey);
}
Private Bytes 这个参数包含着进程的托管的和本地的内存。
你也可以使用任务管理器来查看,切换到进程选项卡,看w3wp.exe这个进程你可以看到“虚拟内存使用量”也增加到了115,000KB 。
|
Process
|
Baseline value
|
New value
|
|
w3wp.exe
|
9,820 KB
|
114,840 KB
|
虚拟内存已经增加了大约100MB,大部分都是asp.net 进程消耗的。
注意任务管理器中的“虚拟内存使用量”和性能监视器中的Process: Private Bytes 相对应。
在统计图中你看以看到Private Bytes 和 # Bytes in all Heaps 的值在同时的增加,这表明增加的内存使用是发生在托管内存中,而不是本地内存。
下图显示了内存增加的过程,
你可以改变y轴的最大值来调整显示比例,在图表上右键,选择 “属性”,点击“图表”选项卡,然后找到并修改 Y 轴最大值。
第二次点击分配
这一步将分配更多的内存,再次点击“Allocate 20 MB Objects”,一共2次,下表显示了新的变化:
|
Field
|
Baseline value
|
One allocation
|
Two allocations
|
|
StartTime
|
07/08/2002 12:14:06 PM
|
07/08/2002 12:40:59 PM
|
07/08/2002 13:25:51 PM
|
|
Age
|
00:00:04.2961776
|
00:00:23:9585520
|
00:47:33.1343328
|
|
ProcessID
|
3,212
|
3,212
|
3,212
|
|
RequestCount
|
0
|
1
|
2
|
|
Status
|
Alive
|
Alive
|
Alive
|
|
ShutdownReason
|
N/A
|
N/A
|
N/A
|
|
PeakMemory Used
|
5,948
|
8,904
|
113,752
|
|
Memory Stats
|
Baseline value
|
One allocation
|
Two allocations
|
|
GC.TotalMemory
|
780 KB
|
100,912 KB
|
200,916 KB
|
|
Private Bytes
|
8,896 KB
|
117,616 KB
|
221,450 KB
|
看看任务管理器中的变化:
|
Process
|
Baseline value
|
One allocation
|
Two allocations
|
|
w3wp.exe
|
9,820 KB
|
114,840 KB
|
216,240 KB
|
“虚拟内存使用量”又增加了100M,大部分还是被asp.net 的进程消耗的。
性能监视器的报告和图表也表达了这个意思。看看 “Cache API Entries”数目,和你在代码中看到的一样,每次点击事件中有一个循环,会创建5个缓存部件,运行了2次,那就是10个。
#Bytes in all Heaps 和 Large Object Heap Size的不同显示了大部分的托管内存被LOH使用了:
第三次点击分配
最后一次的点击耗费了更多的内存,快速的切换到任务管理器中的“性能”选项卡,检查“页面文件使用记录”(win200种是“内存使用量”)。
每次点击“Allocate 20 MB Objects”按钮,图表显示有一个明显的斜度(增加趋势),并保持一定的水平直到下一次点击,如果这个进程被回收了,你可以看见一个明显的下降,下图显示了这个现象:
任务管理器的这些现象可以让我猜测系统上的一些性能变化。你可以查看应用程序日志来确认发生了进程回收:
Event Type: Error
Event Source: ASP.NET 1.0.3705.0
Event Category: None
Event ID: 1001
Date: 6/7/2002
Time: 1:04:14 AM
User: N/A
Computer: machinename
Description: aspnet_wp.exe (PID: 2740) was recycled because
memory consumption exceeded the 306 MB
(60 percent of available RAM).
回到asp.net 的页面中,点击“Refresh Stats”按钮,你可以看见第二个表格被加入到页面中来,它包含了新进程的数据,进程健康监视器关闭了原先的进程,因为达到了内存限制值,然后启动了一个新进程。比较两个表格的数据,特别的,看看Status 和 ShutdownReason和 Updated Memory Stat
如果还有什么不清楚的,请重复以上的操作来增加认识。
发表于 @ 2008年01月25日 12:39:00|评论(loading...)|编辑