Silverlight内存泄露(二)检测内存泄露
理解GC
理解了GC如果工作,.net程序中的许多奇怪问题都容易理解了。虽然.net一直声称.net程序不需要显式的内存管理,GC会在后台释放不需要的对象,开发者不需要管理对象,但是.net 程序仍然需要关注内存泄露和性能问题。尤其Silverlight、WPF的内存泄露比其他.net程序严重的多。
GC解决了非托管程序中常出现的内存泄露问题,比如开发者忘记释放自己创建的资源。既然GC可以释放内存,为什么还会发生内存泄露呢?原因只有一个,GC与开发者在对象是否生存(”live”)、是否正被使用上有不同的认识。开发者认为对象无用了,可以被GC回收,但GC却认为对象正被使用,内存不会被回收。因此必须明白GC工作机制。
下面简单介绍GC,更多内容参见其他资料。
1. 大对象与小对象
对象大于85k或多维数组大于8k,对象将被分配到LOH堆。
其他对象是小对象。
2. GCroot
简单说:GC释放内存从Root开始,只要Root可达的对象都被认为live,不会被回收。
GCroot不是对象,任何被GCroot引用的对象都可以自动在下一次的GC中存活,有四种GCRoot:
a) 方法中的本地变量,在方法执行期内被认为是root。
b) 静态变量总被认为是Root:thread static 在线程生命期内不会被回收,其他类型将持续到程序结束。
c) 托管对象被传递到非托管代码中,托管对象被认为是ROOt。
d) 实现了finalizer的对象,在GC以后,虽然GC认为对象已经不存活了,但是再执行finalizer之前,此类对象也被认为是ROOT。因此如果检查到finalizer对象仍然存在,并不一定是内存泄露,下一次GC才会回收。
3. 对象图
对象在内存的关系构成了对象图,一个对象可能存在多个从root到此对象的路径,释放此对象,必须切断所有引用。
上图:classC存在三个路径,root1->ClassC、root2->ClassC、root3->Class
B->ClassC.
释放ClassC的内存必须切断三个路径。
可以看出内存泄露通常是开发者忘记或者没有意识到一个对象可能存在多个根。
4. GC 代
5. LOH大对象堆
对象大于85k或多维数组大于8k,对象将被分配到LOH,大对象不会被压缩,容易形成碎片,将抛出OutOfMemoryException,大对象仅在执行完全回收时执行,即在Gen2时回收。大对象产生的内存问题,不讨论。
检测内存泄露
许多工具可以检测内存泄露,现在使用ANTS Memory Profiler工具。
检测内存泄露步骤
1. 了解自己的代码,什么地方容易发生内存泄露。
对于Silverlight,可以关注UI。由于这次检测的阅读器程序,采用了MVVM架构,可能发生泄露的地方, MVVM结构简图:
可以看出,最容易出现内存泄露的地方是View。
2. 打开ANTS Memory Profiler,New Profile Session
Uri选择html或者xap地址,如果是OOB程序,必须保证OOB没有安装到本地,否则会显示:
3. 使用ANTS 发现泄露的步骤
a) 多执行几次跳转等操作,获取内存快照。
b) Class List 视图中查找View
可看到View实例为2,应该为1。
c) Instance Cataegorizer 视图查看实例类别,ANTS会对GCRoot到当前对象的所有Instace根据路径分类。在此可以对实例Live情况有个基本认识。
上图显示两个类别。其中第一个可能是内存泄露。
d) Instance List 视图,重新执行可能引起内存泄露的操作,再次获取快照,如下图:
可看到第一个实例是上次GC没有被回收的对象。
除了可以根据代码估算内存泄露对象外,一般距离GC远的可能是泄露对象,Gen 越大越可能是泄露对象。
e) Instance Retention Graph 视图,显示从Root到当前对象的所有路径。解决内存泄露问题,就从这里着手了。
通过比较两图,找到可能泄露的地方,主要着手点是上次没被回收的对象图。
注:下图不必细看,用以说明Instance Retention Graph 视图内容,在下一篇会细说。
对象保存在缓存。
技巧
1. GC回收时间不可控制,可以增加GC回收代码:
GC.Collect();
GC.WaitForPendingFinalizers();//因为为Finalize对象,再第一次GC后仍然作为root存在,在下次GC时才会释放内存。
GC.Collect();//确保Finalize对象被释放
2.执行两次快照操作