• .net无用单元回收


           我们平常写程序很少自己去写资源管理的,除非写非常大型的应用程序,或者大公司自己的sdk。看到过PGP源代码的一定知道,PGP的SDK就实现了自己的内存管理。自己管理内存烦恼实在多多,忘记释放了,释放了又再次访问的bug层出不穷,这种bug又非常难查。普通的逻辑bug,简单测试发现程序没有按照预想的运行就可以找到。但是内存的问题却很难发现。过去很多公司也为解决这方面的问题作过很大努力,比如Compuware的BoundsChecker,Rational的Purify。这些工具使用起来也很困难,经常找到的点都是你用的开发库的代码。现在.NET提供了全套的资源管理(无用资源回收:Garbage Collection,简称GC),能够让我们从中解脱出来,把精力用在自己应该解决的业务问题上去。当然它不是万能的,我们最好多了解他的原理,以便我们可以更好的享用它。

    系统资源不是无限的,内存用完要释放,文件,网络连接都是系统资源,用完之后也要释放。在面向对象的系统中,所有的东西都是对象,所以使用任何资源都要从系统分配内存,最后释放它。使用资源的过程无外乎五个步骤:

    1.         为代表资源的类型分配内存;

    2.         初始化资源状态,请求非内存系统资源(如打开文件,建立网络连接等);

    3.         通过访问类型的实例(对象)及其成员变量、方法等来访问资源;(可能多次)

    4.         清空资源状态,释放系统资源(如关闭文件,关闭网络连接等);

    5.         释放内存

    我们遇到的内存问题一般都在上面的五个步骤中,.NET提供的无用单元回收(GC)机制基本上都可以解决这些问题。不过GC是不知道如何清空资源状态和释放系统资源的(即上面的第四步),这就要利用到Fina

    CLR实现了一个托管堆(Managed Heap),要求所有的资源都必须从这个堆中分配,并且无需释放。下面就详细说明对象是如何在堆中分配,GC又是如何做无用单元回收的。

    CLR在进程初始化时,保留一块连续的内存,这块连续的内存就是托管堆。CLR同时为托管堆维护一个指针,这个指针永远指向下一个可以分配的内存空间,我们这里叫NextObjPtr。

    当程序适用new创建一个对象时,new首先确认堆中是否有足够的内存空间,如果有的话,则为对象分配空间,调用对象的构造函数,返回分配空间的地址,接着NextObjPtr指向剩余空间的地址,即下一个可以分配的地址。如下图:

    图中虚线是NextObjPtr的起始地址,分配对象C成功并返回地址后,NextObjPtr移到实线的位置。

    再 让我们看看普通应用程序的堆内存分配方式。普通的内存分配方式维护一个空闲内存列表,系统首先遍历空闲空间列表,找到一个足够大的空间,然后将其拆分出足 够的空间分配,然后再将剩下的空间加入到空闲空间列表。在历史上有很多的实现进程堆内存分配的算法,比如比较著名的二分法等等。但是比较来看,.NET的内存分配方法要快的多。

    不过内存不是无限的,堆的空间分配光了怎么办?CLR在分配内存的时候,如果发现堆中的空闲空间不足时,就会启动无用空间回收。GC将堆中不再被使用的对象占用的内存释放掉,然后将堆整理,使其剩下连续的空间以待分配,当然如果没有可以释放的对象或者释放后内存还是不够的话,就抛出OutOfMemoryException异常。GC是如何断定一个对象不再被使用了呢?

    每一个应用都有一组根,这些根包括了标示托管堆中一组对象的存储单元。被认为是根的对象包括:

    1.         所有的全局和静态对象;

    2.         一个线成栈中的局部对象和函数的参数;

    3.         任何CPU寄存器包含的指针指向的对象;

    上面根的列表由JIT和CLR维护并且GC可以访问。

    开始无用单元回收后GC就开始遍历根,找到第一个根指向的对象,沿着这个对象向下找,找到这个对象所有引用的对象,以及引用之引用,并将其放入到一个集合中。这一个完成后,就接着找下一个根。一旦GC发现某个对象已经在集合中,就停止这个分支的搜寻以防止重复和引用死循环。

    完成后,GC就有了一个所有根可以访问到的对象的集合,这个集合中没有的对象就认为是无用的。如下图:

    GC的集合包括了对象A、B、D、F,而对象C、E、G就是无用对象,GC将释放其资源。GC接着遍历堆中的所有对象,释放无用对象,并将有用对象向内存的地位移动(据说使用的memcpy),以保证空闲空间的连续性。NextObjPtr就被指向空闲空间的开始地址。这样做会使一些对象的引用地址失效,GC负责更正这些指针。回收后的堆如下图:

    这一次回收所作的工作不可谓不复杂,消耗的CPU时间也是很多。不过还好,它不是时时刻刻都在运行,而是只在堆满了之后才回收(实际上是Generation 0满了之后,Generation我将在接下来的文章讨论),其他分配内存的时候还是非常快的。而且.NET提供丰富的设置来提高无用单元回收的效率。

    FinaLize

    在上一篇文章中我分配使用资源一共五步,我们已经知道了GC是如何释放无用对象的内存了。但是它怎么实现第四步清空资源使用状态、释放利用到的一些非内存的系统资源呢?.NET引入了Fina

    GC在无用单元回收时一旦发现某个对象有Fina

    我们可以用两种方法来写自己的Fina

    代码1

    pub

    {

       pub

       {

       }

       protected override void Fina

       {

          Console.WriteLine(“Fina

       }

    }

    使用这种方法时要注意一点,.NET不会帮你做调用基类的Fina

    代码2

    pub

    {

       pub

       {

       }

       protected override void Fina

       {

          Console.WriteLine(“Fina

          base.Fina

       }

    }

    另外一种方法就是析构函数。C#中的析构函数不同于C++。我们看下面的代码:

    代码3

    pub

    {

       pub

       {

       }

       ~SomeClass()

       {

          Console.WriteLine(“Fina

       }

    }

    它等同于代码2。

    使用Fina

    GC是如何实现Fina

    当托管堆的内存不足的时候,GC开始对堆进行回收。GC回收一个对象前,先检查Fina

    对象G和对象E不在根的范围之内,被回收。对象F和对象C由于需要Fina

    这时对象F和对象C不再是根的一部分,如果此时GC进行回收,将会被认作无用对象进行回收,回收后如下图:

    上面简单描述了Fina

    l         Generation

    每次都对整个对进行搜索,压缩是非常耗时的。微软总结了一些过去的开发中出现的现象,其中有一条就是,越是新的对象,越是最快被丢弃不再使用。微软根据这个经验在内存回收中引入了Generation的概念,我此处暂时将其翻译成代。托管堆开始的时候是空的,程序启动开始在其中分配对象,这时候的对象就是第0代(Generation 0)对象。如下图:

    接下来,到托管堆空间不足,GC进行了第一次回收,剩下的没有被回收的对象就升为第一代,之后再新分配的对象就是第0代(图甲)。再之后GC再进行回收的话只回收第0代,未被回收的第0代升级为第一代,原来的第一代升级为第0代(图乙)。

    GC缺省的代(Generation)最高就是2,升级到第二代就不会再升级了。那什么时候GC回收第一,第二代呢?当GC回收完第0代后,发现内存空间还不够,就会回收第一代,回收完第一代,还不够,就回收第二代。

    这一篇也写了不少了,所以下一篇再继续,下一篇写WeakReference和如何在自己的代码中控制GC的动作。

    这篇文章接着上一次的来,继续讨论无用资源回收的其它一些话题。

    l         WeakReference(弱引用)

    我们平常用的都是对象的强引用,如果有强引用存在,GC是不会回收对象的。我们能不能同时保持对对象的引用,而又可以让GC需要的时候回收这个对象呢?.NET中提供了WeakReference来实现。弱引用使用起来很简单,看下面的代码:

    代码1

    Object obj = new Object();

    WeakReference wref = new WeakReference( obj );

    obj = null;

    第一行代码新建了一个新的对象,这里叫它对象A,obj是对对象A的强引用。接着第二行代码新建了一个弱引用对象,参数就是对象A的强引用,第三行代码释放掉对对象A的强引用。这时如果GC进行回收,对象A就会被回收。

    怎样在取得对象A的强引用呢?很简单,请看代码2:

    代码2

    Object obj2 = wref.Target;

    if( obj2 != null )

    {

       … // 做你想做的事吧。

    }

    else

    {

       …// 对象已经被回收,如果要用必须新建一个。

    }

    只要显示的将弱引用的Target属性附值就会得到弱引用所代表对象的一个强引用。不过在使用对象之前要对其可用性进行检查,因为它可能已经被回收了。如果你得到的是null(VB.NET下为Nothing),表明对象已经被回收,不能再用了,需要重新分配一个。如果不是null,就可以放心大胆的用了。

    接下来让我们看WeakReference的另外一个版本,请看代码3:

    代码3

    // pub
                
                

    //   object target,

    //   bool trackResurrection

    //);

    Object obj1 = new Object();

    Object obj2 = new Object();

    WeakReference wref1 = new WeakReference( obj1, false );

    WeakReference wref2 = new WeakReference( obj2, true );

    WeakReference的另外一个版本有两个参数,第一个参数和我们前面用的版本的一样。第二个参数让我们看一下他的原型,bool trackResurrection,跟踪复活,是个bool型,就是是否跟踪复活。前面的文章中我提到过需要Fina

    现在让我们看看WeakReference是如何实现的。很显然WeakReference不能直接的引用目标对象,WeakReference的Target属性的get/set是两个函数,从某处查到目标对象的引用返回,而不是我们最常用写的那样直接返回或者设置一个私有变量。GC维护了两个列表来跟踪两种弱引用的目标对象,在一个WeakReference对象创建时,它在相应的列表中找到一个位置,将目标对象的引用放入,很显然,这两个列表不是根的一部分。在GC进行内存回收的时候,如果要回收某一个对象,会检查弱引用的列表,如果保存着这个对象的引用,则将其设为null。

    l         控制GC行为

    .NET提供了System.GC类来控制GC的行为,GC只提供静态方法,无需也不能(GC的构造方法被做成私有)创建它的实例。

    GC类提供的最主要一个方法就是Collect,它使自己控制内存回收成为可能。Collect方法有两种版本,void Collect(); 和 void Collect(int);。第二个版本的Collect提供一个参数,让你选择是回收那一代(Generation)以及比其年轻者的对象,也就是说GC.Collect(0)只回收第0代的对象,而GC.Collect(1)则是要回收第0代和第一代的对象。Collect()则是回收所有对象,等同于GC.Collection(GC.MaxGeneration)。MaxGeneration是GC唯一的一个属性,它给出GC的最高代。

    GC类提供了另外一个方法来获取某个对象的代值,GetGeneration。代码4给出了一段例子代码,可以让我们更好的理解Generation和GC提供的这两个方法。请看代码4:

    代码4

    class GCDemoClass

         {

             ~GCDemoClass()

             {

                  Console.WriteLine( "Demo Class Fina

             }

    }

    static void

             {

                  GCDemoClass inst = new GCDemoClass();

                  Console.WriteLine( "Generation of demo object:{0} ",  GC.GetGeneration( inst ) );

                  GC.Collect();

                  Console.WriteLine( "Generation of demo object:{0} ",  GC.GetGeneration( inst ) );

                  GC.Collect();

                  Console.WriteLine( "Generation of demo object:{0} ",  GC.GetGeneration( inst ) );

                  inst = null

                 

                  GC.Collect( 0 );

                  Console.WriteLine( " After collect generation 0 ..." );

                  GC.Collect( 1 );

                  Console.WriteLine( " After collect generation 1 ... " );

                  GC.Collect( 2 );

                  Console.WriteLine( " After collect generation 2 ... " );

                  Console.ReadLine();

             }

    GCDemoClass实现了一个析构函数,根据我前面文章提到的,编译器会将其变为Fina

    GC还提供了其他的一些方法,这里就不再讨论了,大家可以去看MSDN。


                                                                                                                            轉自:http://www.upschool.com.cn/edu/1317/2005/528/10du246616_1.shtml

  • 相关阅读:
    ASP.NET中26种性能优化方法
    ASP.NET中常用的26个优化性能方法
    ASP.NET页面事件:顺序与回传详解
    ASP.NET控件属性大全
    ADO.NET数据库编程
    ado.net笔记
    98内存管理
    23个常用正则表达式(数值和字符串)
    web中用纯CSS实现筛选菜单
    Unity3D脚本:C#计时类脚本
  • 原文地址:https://www.cnblogs.com/scottckt/p/813788.html
Copyright © 2020-2023  润新知