.NET的托管堆中是否可能出现内存泄漏现象

来源:互联网 发布:淘宝 药品 线下付款 编辑:程序博客网 时间:2024/05/21 17:35
 .NET的托管堆中是否可能出现内存泄漏现象
本文节选自《.NET程序员面试指南》一书

    这一问题的难度比较大,主要注重的是应聘人员的程序开发经验。如果切身经历过一些内存泄漏的情况,那应聘者将比较容易回答此类问题。无论如何,读者至少应该了解,即使在拥有垃圾处理机制的托管堆上,仍然是可能发生内存泄漏现象的。
  所涉及到的知识点
•    什么是内存泄漏
•    一些常见的导致托管堆内存泄漏的情况
  分析问题
    在一些不规范的编码情况下,即使是拥有垃圾回收和内存管理机制的托管堆上,仍然可能出现内存泄漏。首先读者需要了解什么是内存泄漏。
    内存泄漏是指内存空间上产生了不再被实际使用却又不能被分配的内存,内存泄漏的意义很广泛,例如程序无意义地保持对象内存、内存碎片、不彻底的对象释放等都属于内存泄漏现象。内存泄漏将导致主机的内存随程序的运行而逐渐减少,无论内存泄漏的具体形式如何,它的危害都是显而易见的,所有的内存泄漏都是需要程序员努力避免的。
按照内存泄漏的定义,大部分时候.NET的托管堆中存在着短暂的内存泄漏情况,那是因为对象一旦不被使用后,需要等到下一个GC时才能被释放。这里,笔者将举出其他几种对系统危害更大的内存泄漏情况:
1)大对象的分配
.NET中所有的大对象将分配在托管堆内一个特殊的区域,这里暂时称呼它为“大对象堆”。在回收大对象堆内的对象时,其他的大对象不会被移动,这是考虑到大规模地移动对象需要耗费过多的资源。这样,在程序过多地分配和释放大对象之后,就会产生很多内存碎片。
,随着对象的分配和释放不断进行,在不进行对象移动的大对象堆内,将不可避免地产生小的内存碎片。程序员对此能做的就是尽量减少大对象的分配次数,尤其是那些作为局部变量的,将被大规模分配和释放的大对象,典型的例子就是String类型,关于String的使用在后续章节将有详细的叙述。
2)不恰当地保存根引用
最简单也最常见的错误做法可能就是不恰当地把一个对象申明为公共静态变量,一个公共的静态变量将一直被GC视为一个在使用的根引用,而更糟糕的情况是,当这个对象内部还包含更多的对象引用时,这些对象同样不会被释放。看一段简单的代码:代码3-19。
代码3-19  保存根引用:RefRoot.cs
namespace NET.MST.Third.RootRef
{
    public class RefRoot
    {
        //这里是一个占用大量内存的成员
        public String[] BigMembor;
        public RefRoot(String content)
        {
            //初始化大对象
            BigMembor = new String[1000];
            for (int i = 0; i < BigMembor.Length; i++)
                BigMembor[i] = content;
        }
    }
    public class MainClass
    {
       //公共静态大对象
        public static RefRoot bigobject = new RefRoot("aaa");
        static void Main()
        {
            Console.Read();
        }
    }
}
在代码3-19中,MainClass定义了一个公共静态的大对象,这个对象将直至程序运行结束后才会被GC释放。这一段代码的设计是否成功将取决于这个公共静态变量被使用的频率以及业务逻辑,如果在整个程序中各个类型不断地使用这个静态成员,那这样的设计有助于减少大对象堆内的内存碎片,但如果整个程序极少地甚至于只有一次使用了这个成员,那考虑到它占用的内存会影响整体系统的性能,设计时应该考虑把他设计成实例变量,以便于GC能够及时地释放它。
这里仍然只从性能的角度考虑问题,实际的设计情况不仅需要考虑性能,还需要考虑程序的和架构和可扩展性。
3)3)不正确的Finalize方法
在前述章节中笔者已经介绍了,Finalize方法由一个专用的线程进行调用,微软并没有公开这部分具体的调度算法,但是有一点却是肯定的,不正确的Finalize方法将导致Finalize方法不能被正确执行,系统中所有的Finalize方法不能被正确执行时,包含它们的对象也只能驻留在托管堆内不能被释放,这样的情况将会导致严重的后果。
Finalize方法应该只致力于快速而简单地释放非托管资源,并且尽可能快地返回。不正确的Finalize方法可能包含这样的代码:
•    没有保护地写文件日志
•    访问数据库
•    访问网络
•    把当前对象赋给某个存活的引用
当Finaize方法访问文件系统、数据库系统或者网络时,将会有资源争用和等待的潜在危险。试想一个试图不断尝试访问离线数据库的Finalize方法,将会在长时间内不会返回,这不仅影响了本身对象的释放,也使得排在Finalize方法队列中的所有后续对象得不到释放,这个连锁反应将很快造成内存耗尽。
而另外一种危险的代码是在Finalize方法中把对象自身又赋给了另外一个存活的引用,这时对象内的一部分资源已经被释放了,而另外一部分则没有,这样一个对象被再次激活后,将导致不可预知的后果。
  答案
.NET的托管堆内可能出现严重内存泄漏现象,主要产生原因有:大对象的频繁分配和释放、不恰当地保留根引用和错误的Finalize方法。


5}JJ l/m8rsm13164110
f.M T6?+~Q13164110【书名】.NET程序员面试指南
【作者】朱毅 等 编著
【ISBN】978-7-121-07675-6
【出版社】电子工业出版社
【出版日期】2008年12月
【宣传语】
从面试的角度来梳理.NET程序员的技术功底。
以项目开发经理的眼光来审视编程知识的掌握。
【内容简介】
本书着重针对.NET技术职位的应聘者,在.NET框架各个技术类别中,选取最常出现在.NET面试中的问题,进行分析和解答,同时解释和剖析和该问题相关的.NET机制原理,帮助读者达到知其然更知其所以然的程度。本书几乎包揽了所有常见的面试题,从基础知识、数据库,再到比较流行的XML、测试方法和算法,是目前市场上唯一一本面试试题集锦。
全书配合了大量的图例及代码说明,非常适合正打算参加.NET技术职位的应聘和希望梳理.NET框架下技术点的读者阅读