1.发现泄露
2.找到被泄露的资源
3.决定在源码中何时何处释放该资源
最直接“发现”泄露的方式是遭受泄露引发的问题
你或许没有见过内存不足。“内存不足”提示信息极少出现。因为 *** 作系统运行中实际内存(RAM)不足时,它会使用硬盘空间来扩展内存。(称为虚拟内存)。
在你的图形应用程序中可能更多出现的是“句柄不足”的异常。准确的异常不是System.ComponentModel.Win32Exception 就是 System.OutOfMemoryException 均包含如下信息:”创建窗体句柄错误”。这两个异常多发于两个资源被同时使用的情况下,通常都因为该释放的对象没有被释放所致。
另外一种你会经常碰到的情况是你的应用程序或是整个系统变更得越来越慢。这种情况的发生是因为你的系统资源即将耗尽。
我来做个生硬的推断:大多数应用程序的泄露在多数时间里都不是个问题,因为由泄露导致出现的问题只在你的应用程序集中使用很长时间的情况下才会出现。
如果你怀疑有些对象在应该被释放后仍逗留在内存中,那需要做的第一件事就是找出这些对象都是什么。
这看起来很明显,但是找起来却不是这样。
建议通过内存工具找到非预期逗留在内存中的高级别对象或是根容器。在项目x中,这些对象可能是类似LayoutView实例一样的对象们(我们使用了MVP(Model View Presentation )模式)。在你的实际项目中,它可能依赖于你的根对象是什么。
下一步就是找出它们该消失却还在的原因。这才是调试器与工具能真正帮忙的。它们可以显示出这些对象是如何链接在一起的。通过查看那些指向“僵尸对象”(the zombie object)的引用你就可以找到引起问题的根本原因了。
你可以选择 ninja方式(译者:间谍方式?)(参照 工具介绍章节中有关 SOS.dll 和 WinDbg 的部分)。
我在项目X中用了JetBrains的dotTrace,本文中我将继续使用它来介绍。在后面的工具相关章节中我会向你更多的介绍该工具。
你的目标是找到最终引起问题的那个引用。不要停留在你找到的第一个目标上,但是也要问问自己为什么这个家伙还在内存中。
常见内存泄露的原因
上面提到的泄露情况在.net中较常见。好消息是造成这些泄露的原因并不多。这意味着当你尝试解决一个泄露问题时,不需要在大量可能的原因间搜寻。
我们来回顾一下这些常见的罪魁祸首,我把它们区别开来:
· 静态引用
· 未注销的事件绑定
· 未注销的静态事件绑定
· 未调用Dispose方法
· Dispose方法未正常完成
除了上列典型的原因外,还有些其它情况也可能引发泄露:
· Windows Forms:绑定源滥用
· CAB:未移除对工作项的调用
我只列出了可能在你应用程序中出现的一些原因,但应该清楚你的应用程序依赖的其它.net代码、库实际使用中也可能引发泄露。
我们来举个例子。在项目x中,使用了一套第三方控件来构造界面。其中一个用来显示所有工具栏的控件,它管理着一个工具栏列表。这种方式没什么,但有一点,即使被管理的工具栏自身实现了IDisposable接口,管理类却永远也不会去调用它的Dispose方法。这是一个bug.幸运的是这发生在一个很容易发现的工作区:只能我们自身来调用所有工具样的Dispose方法了。不幸的是这还不够,工具栏类自身问题也不少:它并没有释放自身承载的控件(按钮,标签等等)。所以在解决方案中还要添加对每个工具栏中控件的释放,但是这次可就没那么简单了,因为工具栏中的每个子控件都不同。不管怎么样这只是一个特殊的例子,我要表达的观点是你应用程序中使用的任何第三方库、组件都可能引发泄漏。
最后,还有一种由.net framework造成的泄露,由一些不好的使用习惯引起。即使.net framework自身可能引发泄露,但这是你极少会遭遇到的情况。把责任推到.net身上很容易,但在我们把问题推到别人头上前,还是应该先从自身写的代码出发,看看里面有没有问题。
常见泄露演示
我已经列举出了泄露主要的来源,但我还不想仅限于此。如果每个泄露我都能举个鲜活的例子的话,我想本文会更实用些。好,我们先启动Vs 和 dotTrace , 然后看些示例代码。我会同时演示如何解决或是避免每个泄露情况。
项目X中使用了CAB和MVP模式,这意味着界面由工作空间、视图和呈现者组成。简单起见,我决定使用包含一组窗口的Winform应用。其中使用了与Jossef Goldberg的一篇关于“Wpf应用程序内存泄露”文章中相同的方法。甚至我会直接把相同的例子和事件处理函数应用到我的Winform App中。
你好,“要是这个程序一直开着并且有人一直在用,占用的内存就会越来越多...直到不可挽回”
这句话我是相当的不理解……发生这样的事,与WPF没有关系吧?应该是程序本身编写的问题导致内存泄露。
GC会自动调出了作用域的类的析构,即使没有写Finalize,也会直接继承基类答早的析构函数的,怎么可能有内存一定释放不掉?
所以,问题只能是变量一直没出作用域,也就是设计上的问题。不知道你的系统是什么类型,如果可能,希望你能补充下问题。
==========================================
MSDN中关于NavigationService类的说明:
By default, NavigationService does not store an instance of a content object in navigation history. Instead, NavigationService creates a new instance of the content object each time it is navigated to by using navigation history. This behavior is designed to avoid excessive memory consumption when large numbers and large pieces of content are being navigated to. Consequently, the state of the content is not remembered from one navigation to the next.
默认状况下,NavigationService并不会在导航历史中保存任何导向的目标内容类的实例,作为替代,它每次都创建目标内容的新实例并保存在导航历史中。这个特性被设计用来防止当导航到大的(占内存大的)的成员或内容页时过度的内存消耗。当然,内容页的状态在导航中不会被保存。
=======分割一下,下面是我说的=====
其实NavigationService就是一个经过改进的特殊的Command模式实现,所做的改进正是为了防止内存消耗。而正因为它是Command模式的实现,它在导航过程中一定会保存与导航次数相等的类,只不过它并没有保存这些类的状态而已。
举个例子,按这个方式导航:Page1->Page2->Page3->Page2->Page4
此时,WPF内部将保存有5个类的实例
所以……导航次数与WPF中保存的Page类(或你的其他页面类)的关系是线性增长的:
Count(PageClass In Memory) = Navigation Times
而且,最过分的是,我没有看到关于BackStack和FowardStack大小的说明。
============再分割一下==============
我以前还真没认真看过方面,写了个代码测试了下,导航过程中增加的内存很小很小。
所以,我觉得你应该可以试着优化一下页面结构,尽量少放静态的内容在里面。
BTW,大概在MS眼中,每个服务器都应该有1TB内存……
MSDN中关于导航的说明:
http://msdn.microsoft.com/zh-cn/library/ms750478(VS.85).aspx#NavigationHistory
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
囧rz
强制回收……我一般Shutdown Server的说……你试试告诉客户每10小时运行一下Shutdown -s -f -t 1 好了~~
:) 玩笑。
按我的理解(MS没说……),导航这玩意应该是跟用户绑定的,用户离开网站的话他的历史堆栈就应该释放了。
我比较在意的是你说的那个Singleton,你用Singleton干嘛啦:)不会是因为Singleton一直拿着历史堆栈的引用导致GC没法回收吧……
刚才突然想到机器里颂亮有个传说中的内存泄露检测软件,运行了一下,一股灰尘……
我这个太旧啦,我在网上看了看,貌似大家都用DotTrace和ANTS Profiler的说~我下了个DotTrace,没玩明白……
这两天被个Delphi的破项目弄郁闷了,你先琢磨下吧,学会了教我野举宽哈~
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)