使用 CLR Profiler (Allocation Profiler)来诊断内存泄露
CLR Profiler是另外一个可以提供更多功能的内存消耗工具。这个工具可以检查托管堆,当一个应用程序正在运行中时。它以图表的形式来展示内存分配。在下面的解说中你可以看到当 大于85k 的对象被分配时都发生了什么。
在使用这个工具来看asp.net 代码前,你先要修改一下 machine.config ,修改在<processModel> 这个节上的用户名,把 machine 改成 system ,这样可以让你打开运行这个工具的权限。运行完后把这个配置再改回去,免得有安全问题。
1) 双击运行CLRProfiler.exe
2) 等对话框出来后,从 File 菜单中 选择 “Profile ASP.NET”。
3) 有很多对话框会跳出来,都是一些 关于 停止 Iis 服务,启动iis 服务的信息,最后会跳出一个“Waiting for ASP.NET worker process to start up”的,这是说你现在可以去运行你的页面了。
5) 去掉 “Porfiling active”选框的勾,关闭 “Allocation Graph for: ASP.NET”窗口,然后选上“Porfiling active”,这个工具现在开始收集有关分配的统计信息。
6) 切换到浏览器,点击“Allocate 20 MB Objects”按钮两次。
7) 切换到工具,然后去掉“Porfiling active”选框的勾。
8) 拖动水平滚动条,看信息。
上图显示了Button::OnClick句柄调用了Memory::btn20MB_Click方法,该方法创建了 191M 的System.Byte的数组。
查看堆的图表
1) 关闭“Allocation Graph for: ASP.NET” 窗口。
2) 切换到 主窗口,然后点击 “Show Heap Now”,出现了“Heap Graph for ASP.NET”窗口。
3) 拖动水平滚动条到最右边。你会看到System.Byte []的分配情况。
倒数第二个方框显示了 System.Byte [] 对象被缓存在了 System.Web cache 中,在Caching.CacheSingle 和 Caching.CacheEntry 的引用树种被引用了。注意Caching.CacheEntry 这个被调用了,它包含了191M,这表明大部分 的内存被Cache占用了。
查看统计图
1) 关闭窗口
2) 在主窗口,点击“Histogram by Age”
3)在左边的面板中是按时间列出活对象的柱状图。拉动水平滚动条直到你看到红色的柱子。
System.Byte [] 的对象是在所有列出的对象中消耗内存最多的。
4) 把鼠标移上去看更多的信息,在代表System.Byte [] 的红色的柱子上右键,然后再弹出的上下文菜单中点击“Show Who Allocated”原先的“Allocation graph for ASP.NET”窗口显示出来了。
CLR Profile 也能够跟踪根树(root tree),展示那些分配了内存的调用者和被调用者。
展示根树信息
1) 点击 “Allocation Graph”出现窗口,右键点击“Memory: btn20MB_Click”条,选择“select callers & callees”,整个调用树都会高亮。
2) 右键点击 高亮的部分,点击“Copy as text to clipboard”然后再记事本中贴出来,下面就是一些信息:
<root>: 191 MB (100.00%)
System.Web.Hosting.ISAPIRuntime::ProcessRequest: 191 MB (100.00%)
System.Web.HttpRuntime::ProcessRequest: 191 MB (100.00%)
System.Web.HttpRuntime::ProcessRequestInternal: 191 MB (100.00%)
System.Web.HttpApplication::System.Web.IHttpAsyncHandler.BeginProcessRequest:
191 MB (99.96%)
System.Web.HttpApplication::ResumeSteps: 191 MB (99.96%)
System.Web.HttpApplication::ExecuteStep: 191 MB (99.96%)
CallHandlerExecutionStep::Execute: 191 MB (99.96%)
System.Web.UI.Page::ProcessRequest: 191 MB (99.96%)
System.Web.UI.Page::ProcessRequest: 191 MB (99.96%)
System.Web.UI.Page::ProcessRequestMain: 191 MB (99.96%)
System.Web.UI.Page::RaisePostBackEvent: 191 MB (99.94%)
System.Web.UI.Page::RaisePostBackEvent: 191 MB (99.94%)
System.Web.UI.WebControls.Button::System.Web.UI.IPostBackEventHandler.RaisePostBac
kEvent: 191 MB (99.94%)
System.Web.UI.WebControls.Button::OnClick: 191 MB (99.94%)
Debugging.Memory:: btn20MB_Click: 191 MB (99.94%)
...
System.Byte []: 191 MB (100.00%)
<bottom>: 191 MB (100.00%)
这个树显示了btn20MB_Click是如何创建大对象的。
当你要结束这次运行,点击“Kill asp.net ”,把<processModel> 的用户名属性改回到machine。
CLR Profiler 检查了托管堆,展示了20MB的大对象如如何创建的。那个函数创建了 System.Byte [] 的 CreateLargeObject函数 在 发布(Release)模式下是不会出现的,因为优化器或内联等因素。这个将会在后面的“调试连接问题”再讲述。在调试(debug)模式,你可以看见 btn20MB_Click 调用了CreateLargeObject,在两种发布模式下,大对象被根化了,(根化意味着不会被GC收集),那就证明了内存是被大对象占用的事实。