zoukankan      html  css  js  c++  java
  • .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

  • 相关阅读:
    利用相关的Aware接口
    java 值传递和引用传递。
    权限控制框架Spring Security 和Shiro 的总结
    优秀代码养成
    Servlet 基础知识
    leetcode 501. Find Mode in Binary Search Tree
    leetcode 530. Minimum Absolute Difference in BST
    leetcode 543. Diameter of Binary Tree
    leetcode 551. Student Attendance Record I
    leetcode 563. Binary Tree Tilt
  • 原文地址:https://www.cnblogs.com/scottckt/p/813788.html
Copyright © 2011-2022 走看看