.net内存泄露

上传人:marr****208 文档编号:156922946 上传时间:2020-12-20 格式:DOC 页数:18 大小:120KB
返回 下载 相关 举报
.net内存泄露_第1页
第1页 / 共18页
.net内存泄露_第2页
第2页 / 共18页
.net内存泄露_第3页
第3页 / 共18页
.net内存泄露_第4页
第4页 / 共18页
.net内存泄露_第5页
第5页 / 共18页
点击查看更多>>
资源描述

《.net内存泄露》由会员分享,可在线阅读,更多相关《.net内存泄露(18页珍藏版)》请在金锄头文库上搜索。

1、.Net内存泄露原因及解决办法4.1 Dispose()的使用 4.2 using的使用 4.3 事件的卸载 4.4 API的调用 1. 4.6弱引用(WeakReference ) 2. 4.7析构函数(Finalize()1. 什么是.Net内存泄露(1).NET 应用程序中的内存您大概已经知道,.NET 应用程序中要使用多种类型的内存,包括:堆栈、非托管堆和托管堆。这里我们需要简单回顾一下。以运行库为目标的代码称为托管代码,而不以运行库为目标的代码称为非托管代码。在运行库的控制下执行的代码称作托管代码。相反,在运行库之外运行的代码称作非托管代码。COM 组件、ActiveX 接口和 Wi

2、n32 API 函数都是非托管代码的示例。COM/COM+组件,ActiveX控件,API函数,指针运算,自制的资源文件.这些的非托管的,其它就是托管的在CLR上编译运行的代码就是托管代码。 非CLR编译运行的代码就是非托管代码 。非托管代码用dispose free using 释放。即使在拥有GC的托管堆上,也有可能发生内存泄漏!堆栈 堆栈用于存储应用程序执行过程中的局部变量、方法参数、返回值和其他临时值。堆栈按照每个线程进行分配,并作为每个线程完成其工作的一个暂存区。垃圾收集器并不负责清理堆栈,因为为方法调用预留的堆栈会在方法返回时被自动清理。但是请注意,垃圾收集器知道在堆栈上存储的对象

3、的引用。当对象在一种方法中被实例化时,该对象的引用(32 位或 64 位整型值,取决于平台类型)将保留在堆栈中,而对象自身却存储于托管堆中,并在变量超出范围时被垃圾收集器收集。非托管堆 非托管堆用于运行时数据结构、方法表、Microsoft 中间语言 (MSIL)、JITed 代码等。非托管代码根据对象的实例化方式将其分配在非托管堆或堆栈上。托管代码可通过调用非托管的 Win32 API 或实例化 COM 对象来直接分配非托管堆内存。CLR 出于自身的数据结构和代码原因广泛地使用非托管堆。托管堆 托管堆是用于分配托管对象的区域,同时也是垃圾收集器的域。CLR 使用分代压缩垃圾收集器。垃圾收集器

4、之所以称为分代式,是由于它将垃圾收集后保留下来的对象按生存时间进行划分,这样做有助于提高性能。所有版本的 .NET Framework 都采用三代分代方法:第 0 代、第 1 代和第 2 代(从年轻代到年老代)。垃圾收集器之所以称为压缩式,是因为它将对象重新定位于托管堆上,从而能够消除漏洞并保持可用内存的连续性。移动大型对象的开销很高,因此垃圾收集器将这些大型对象分配在独立的且不会压缩的大型对象堆上。有关托管堆和垃圾收集器的详细信息,请参阅 Jeffrey Richter 所著的分为两部分的系列文章“垃圾收集器:Microsoft .NET Framework 中的自动内存管理”和“垃圾收集器

5、 第 2 部分:Microsoft .NET Framework 中的自动内存管理”。虽然该文的写作是基于 .NET Framework 1.0,而且 .NET 垃圾收集器已经有所改进,但是其中的核心思想与 1.1 版或 2.0 版是保持一致的。可能很多.NET的用户(甚至包括一些dot Net开发者)对Net的内存泄露不是很了解,甚至会说.Net不存在内存泄露,因为“不是有GC机制吗?-”恩,是有这么回事,它可以让你在通常应用中不用考虑令人头疼的资源释放问题,但很遗憾的是这个机制不保证你开发的程序就不存在内存泄露。甚至可以说,dot Net中内存泄露是很常见的。这是因为: 一方面,GC机制本

6、身的缺陷造成的;另一方面,Net中托管资源和非托管资源的处理是有差异的,托管资源的处理是由GC自动执行的(执行时机是不可预知的),而非托管资源 (占少部分,比如文件操作,网络连接等)必须显式地释放,否则就可能造成泄露。综合起来说的话,由于托管资源在Net中占大多数,通常不做显式的资源释放是可以的,不会造成明显的资源泄露,而非托管资源则不然,是发生问题的主战场,是最需要注意的地方。 另外,很多情况下,衰老测试主要关注的是有没有内存泄露的发生,而对其他泄露的重视次之。这是因为,内存跟其他资源是正相关的,也就是说没有内存泄露的发生,其他泄露的发生概率也较小,其根本原因在于几乎所有的资源最后都会在内存

7、上有所反应。 一提到托管代码中出现内存泄漏,很多开发人员的第一反应都认为这是不可能的。毕竟垃圾收集器 (GC) 会负责管理所有的内存,没错吧?但要知道,垃圾收集器只处理托管内存。基于 Microsoft .NET Framework 的应用程序中大量使用了非托管内存,这些非托管内存既可以被公共语言运行库 (CLR) 使用,也可以在与非托管代码进行互操作时被程序员显式使用。在某些情况下,垃圾管理器似乎在逃避自己的职责,没有对托管内存进行有效处理。这通常是由于不易察觉的(也可能是非常明显的)编程错误妨碍了垃圾收集器的正常工作而造成的。作为经常与内存打交道的程序员,我们仍需要检查自己的应用程序,确保

8、它们不会发生内存泄漏并能够合理有效地使用所需内存。2 内存泄漏的种类及原因(1)堆栈内存泄漏虽然有可能出现堆栈空间不足而导致在受托管的情况下引发 StackOverflowException 异常,但是方法调用期间使用的任何堆栈空间都会在该方法返回后被回收。因此,实际上只有在两种情况下才会发生堆栈空间泄漏。一种情况是进行一种极其耗费堆栈资源并且从不返回的方法调用,从而使关联的堆栈帧无法得到释放。另一种情况是发生线程泄漏,从而使线程的整个堆栈发生泄漏。如果应用程序为了执行后台工作而创建了工作线程,但却忽略了正常终止这些进程,则可引起线程泄漏。默认情况下,最新桌面机和服务器版的 Windows 堆

9、栈大小均为 1MB。因此如果应用程序的 Process/Private Bytes 定期增大 1MB,同时 .NET CLR LocksAndThreads/# of current logical Threads 也相应增大,那么罪魁祸首很可能是线程堆栈泄漏。下图 显示了(恶意的)多线程逻辑导致的不正确的线程清理示例。Figure清理错误线程 using System;using System.Threading;namespace MsdnMag.ThreadForker class Program static void Main() while(true) Console.WriteL

10、ine( Press to fork another thread.); Console.ReadLine(); Thread t = new Thread(new ThreadStart(ThreadProc); t.Start(); static void ThreadProc() Console.WriteLine(Thread #0 started., Thread.CurrentThread.ManagedThreadId); / Block until current thread terminates - i.e. wait forever Thread.CurrentThrea

11、d.Join(); 当一个线程启动后会显示其线程 ID,然后尝试自联接。联接会导致调用线程停止等待另一线程的终止。这样该线程就会陷入一个类似于先有鸡还是先有蛋的尴尬局面之中 线程要等待自身的终止。在任务管理器下查看该程序,会发现每次按 时,其内存使用率会增长 1MB(即线程堆栈的大小)。每次经过循环时,Thread 对象的引用都会被删除,但垃圾收集器并未回收分配给线程堆栈的内存。托管线程的生存期并不依赖于创建它的 Thread 对象。如果您只是因为丢失了所有与 Thread 对象相关联的引用而不希望垃圾收集器将一个仍在运行的进程终止,这种不依赖性是非常有好处的。由此可见,垃圾收集器只是收集 T

12、hread 对象,而非实际托管的线程。只有在其 ThreadProc 返回后或者自身被直接终止的情况下,托管线程才会退出(其线程堆栈的内存不会释放)。因此,如果托管线程的终止方式不正确,分配至其线程堆栈的内存就会发生泄漏。(2)非托管堆内存泄漏如果总的内存使用率增加,而逻辑线程计数和托管堆内存并未增加,则表明非托管堆出现内存泄漏。我们将对导致非托管堆中出现内存泄漏的一些常见原因进行分析,其中包括与非托管代码进行互操作、终结器被终止以及程序集泄漏。与非托管代码进行互操作:这是内存泄漏的起因之一,涉及到与非托管代码的互操作,例如在 COM Interop 中通过 P/Invoke 和 COM 对象

13、使用 C 样式的 DLL。垃圾收集器无法识别非托管内存,而正是在托管代码的编写过程中错误地使用了非托管内存,才导致内存出现泄漏。如果应用程序与非托管代码进行互操作,要逐步查看代码并检查非托管调用前后内存的使用情况,以验证内存是否被正确回收。如果内存未被正确回收,则使用传统的调试方法在非托管组件中查找泄漏。终结器被终止:当一个对象的终结器未被调用,并且其中含有用于清理对象所分配的非托管内存的代码时,会造成隐性泄漏。在正常情况下,终结器都将被调用,但是 CLR 不会对此提供任何保证。虽然未来可能会有所变化,但是目前的 CLR 版本仅使用一个终结器线程。请考虑这样一种情况,运行不正常的终结器试图将信

14、息记录到脱机的数据库。如果该运行不正常的终结器反复尝试对数据库进行错误的访问而从不返回,则“运行正常”的终结器将永远没有机会运行。该问题会不时出现,因为这取决于终结器在终结队列中的位置以及其他终结器采取何种行为。当 AppDomain 拆开时,CLR 将通过运行所有终结器来尝试清理终结器队列。被延迟的终结器可阻止 CLR 完成 AppDomain 拆开。为此,CLR 在该进程上做了超时操作,随后将停止该终止进程。但是这并不意味着世界末日已经来临。因为通常情况下,大多数应用程序只有一个 AppDomain,而只有进程被关闭才会导致 AppDomain 的拆开。当操作系统进程被关闭,操作系统会对该

15、进程资源进行恢复。但不幸的是,在诸如 ASP.NET 或 SQL Server 之类的宿主情况下,AppDomain 的拆开并不意味着宿主进程的结束。另一个 AppDomain 会在同一进程中启动。任何因自身终结器未运行而被组件泄漏的非托管内存都将继续保持未引用状态,无法被访问,并且占用一定空间。因为内存的泄漏会随着时间的推移越来越严重,所以这将带来灾难性的后果。在 .NET 1.x 中,唯一的解决方法是结束并重新启动该进程。.NET Framework 2.0 中引入了关键的终结器,指明在 AppDomain 关闭期间,终结器将清理非托管资源并必须获得运行的机会。有关详细信息,请参阅 Stephen Toub 的文章:“利用 .NET Framework 的可靠性功能确保代码稳定运行”。程序集泄漏:程序集泄漏相对来说要常见一些。一旦程序集被加载,它只有在 AppDomain 被卸载的情况下才能被卸载。程序集泄漏也正是由此引发的。大多数情况下,除非程序集是被动态生成并加载的,否则这根本不算个问题。下面我们就来看一看动态代码生成造成的泄漏,特别要详细分析 XmlSerializer 的泄漏。动态代码生成有时会泄漏我们需要动态生成代码。也许应用程序具

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 其它相关文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号