zoukankan      html  css  js  c++  java
  • .net垃圾回收机制编程调试试验

    1. 什么是CLR GC?

    它是一个基于引用跟踪和代的垃圾回收器。

    从本质上,它为系统中所有活跃对象都实现了一种引用跟踪模式,如果一个对象没有任何引用指向它,那么这个对象就被认为是垃圾对象,并且可以被回收。GC通过代的概念来跟踪对象的持续时间,活跃时间段的对象被归为0代,而活跃时间更长的被归为1代和2代。CLR认为大多数对象都是短暂活跃的,因此0代被收集的频率远远高于1代和2代。

    看下GC中对象及其代龄分布:

    在.net中,初始分配的小对象在0代上; 通过垃圾回收后,存活的有根对象将被移动到后一代上。

    有根对象(引用对象)有哪些?

    1.静态、全局对象,包括缓存和进程内Session

    2.Com对象计数器 

    3.线程堆栈上的局部变量钉扣对象,

    4.本地API调用,Remoting/Webservice调用

    5.finalizer 队列里的对象

    先来一个简单的实例程序,

     1 public class Student
     2     {
     3         public string Name { get; set; }
     4         public string Address { get; set; }
     5         public Student(string name, string address)
     6         {
     7             Name = name;
     8             Address = address;
     9         }
    10     }
    11     class Program
    12     {
    13         static void Main(string[] args)
    14         {
    15             Student wang = new Student("wang", "Beijing");
    16             Student lee = new Student("lee", "Shanghai");
    17 
    18             //GC.Collect(); 
    19             Console.ReadLine();
    20         }
    21     }

    Run Windbg, 看下:

    !eeheap -gc

    看下每一代在CLR 堆中的起始地址,输出关于GC的信息:

    Number of GC Heaps: 1
    generation 0 starts at 0x0000000002621030
    generation 1 starts at 0x0000000002621018
    generation 2 starts at 0x0000000002621000
    ephemeral segment allocation context: none
             segment            begin         allocated             size
    0000000002620000 0000000002621000  0000000002625fe8 0x0000000000004fe8(20456)
    Large object heap starts at 0x0000000012621000
             segment            begin         allocated             size
    0000000012620000 0000000012621000  0000000012627048 0x0000000000006048(24648)
    Total Size            0xb030(45104)
    ------------------------------
    GC Heap Size            0xb030(45104)

    看红色字体,得知:

    “第2代的起始地址是 0x0000000002621000,第0代的起始地址是 0x0000000002621030”。 这里暂时先记下,留着后面还会在看。

    切换到主线程, 以便看当前程序堆栈,

    ~0s

    关键时候到了,!clrstack -a,继续看:

     1 0:000> !clrstack -a
     2 OS Thread Id: 0x3f70 (0)
    >...省略无关内容...
    51 
    52 00000000002ceef0 000007ff001701e8 System.IO.TextReader+SyncTextReader.ReadLine()
    53     PARAMETERS:
    54         this = 0x0000000002625a98
    55 
    56 00000000002cef50 000007fee82dc6a2 Test.Program.Main(System.String[])
    57     PARAMETERS:
    58         args = 0x0000000002623598
    59     LOCALS:
    60         0x00000000002cef70 = 0x0000000002623658
    61         0x00000000002cef78 = 0x0000000002623678

    当程序实例化两个Student对象,执行完 Student lee = new Student("lee", "Shanghai") 后,
    这里保存了2个对象,嘿嘿:

    59     LOCALS:
    60         0x00000000002cef70 = 0x0000000002623658
    61         0x00000000002cef78 = 0x0000000002623678

    看到了,第一个对象的地址是:0x0000000002623658, 而前面说,第0代的起始地址是 0x0000000002621030, 很显然,这两个对象被分配在了0代,且占用了0x20(32)个字节。

    不相信,那我们来验明下正身,

    0:000> .load C:Symbolssosex_64sosex.dll
    0:000> !gcgen 0x0000000002623658
    GEN 0

    呵呵,还要继续验身么,继续...

    ! do 0x0000000002623658,

    0:000> !do 0x0000000002623658
    Name: Test.Student
    MethodTable: 000007ff00033538
    EEClass: 000007ff001623a8
    Size: 32(0x20) bytes
     (D:TestPInvokeCPPTestinDebugTest.exe)
    Fields:
                  MT    Field   Offset                 Type VT     Attr            Value Name
    000007fee7577d90  4000001        8        System.String  0 instance 00000000026235b8 <Name>k__BackingField
    000007fee7577d90  4000002       10        System.String  0 instance 00000000026235e0 <Address>k__BackingField

    验到了, 就是Test.Student类的实例,Name和Address字段都看到了。

    开个小差,看看这个实例的内容,

     !do 00000000026235b8,

    0:000> !do 00000000026235b8 
    Name: System.String
    MethodTable: 000007fee7577d90
    EEClass: 000007fee717e560
    Size: 34(0x22) bytes
     (C:WindowsassemblyGAC_64mscorlib2.0.0.0__b77a5c561934e089mscorlib.dll)
    String: wang
    Fields:
                  MT    Field   Offset                 Type VT     Attr            Value Name
    000007fee757f000  4000096        8         System.Int32  1 instance                5 m_arrayLength
    000007fee757f000  4000097        c         System.Int32  1 instance                4 m_stringLength
    000007fee75797d8  4000098       10          System.Char  1 instance               77 m_firstChar
    000007fee7577d90  4000099       20        System.String  0   shared           static Empty
                                     >> Domain:Value  0000000000bcb1d0:0000000002621308 <<
    000007fee7579688  400009a       28        System.Char[]  0   shared           static WhitespaceChars
                                     >> Domain:Value  0000000000bcb1d0:0000000002621a98 <<
    0:000> !do 00000000026235e0 
    Name: System.String
    MethodTable: 000007fee7577d90
    EEClass: 000007fee717e560
    Size: 40(0x28) bytes
     (C:WindowsassemblyGAC_64mscorlib2.0.0.0__b77a5c561934e089mscorlib.dll)
    String: Beijing
    Fields:
                  MT    Field   Offset                 Type VT     Attr            Value Name
    000007fee757f000  4000096        8         System.Int32  1 instance                8 m_arrayLength
    000007fee757f000  4000097        c         System.Int32  1 instance                7 m_stringLength
    000007fee75797d8  4000098       10          System.Char  1 instance               42 m_firstChar
    000007fee7577d90  4000099       20        System.String  0   shared           static Empty
                                     >> Domain:Value  0000000000bcb1d0:0000000002621308 <<
    000007fee7579688  400009a       28        System.Char[]  0   shared           static WhitespaceChars
                                     >> Domain:Value  0000000000bcb1d0:0000000002621a98 <<
    0:000> !do 00000000026235b8 

     果然, 是家住“beijing”的"wang" 同学!

    通过这个事例,我们验证了,在.net中,初始分配的小对象在0代上。

    那么假设,我们执行GC.Collect(),结果会怎样?

    1  static void Main(string[] args)
    2         {
    3             Student wang = new Student("wang", "Beijing");
    4             Student lee = new Student("lee", "Shanghai");
    5 
    6             GC.Collect(); 
    7             Console.ReadLine();
    8         }

    不一步步看了,给明结果吧:

    000000000032eb50 000007fee82dc6a2 Test.Program.Main(System.String[])
        PARAMETERS:
            args = 0x00000000025d3598
        LOCALS:
            0x000000000032eb70 = 0x00000000025d3658
            0x000000000032eb78 = 0x00000000025d3678
    
    0:000> !gcgen 0x00000000025d3658
    GEN 1
    0:000> !gcgen 0x00000000025d3678
    GEN 1

    2 Dispose,Finalization(终结器)
     

    Dispose:用于处置那些占用非托管资源的对象。

    Finalization(终结器): 这是CLR提供的一种机制,允许对象在GC回收其内存之前执行一些清理工作。

    当客户端记得的时候使用IDisposable接口释放你的非受控资源,当客户端忘记的时候防护性地使用终结器(finalizer)。它与垃圾收集器(Garbage Collector)一起工作,确保只在必要的时候该对象才受到与终结器相关的性能影响。这是处理非受控资源的一条很好的途径,因此我们应该彻底地认识它。如果你的类使用了非内存资源,它就必须含有一个终结器。你不能依赖客户端总是C#调用Dispose()方法。因为当它们忘记这样做的时候,你就面临资源泄漏的问题。没有调用Dispose是它们的问题,但是你却有过失。

    用于保证非内存资源被正确地释放的唯一途径是创建终结器。

    调用Dispose()方法的实现(implementation)负责下面四个事务:

    1.释放所有的非受控资源。

    2.释放所有的受控资源(包括未解开事件)。

    3.设置标志表明该对象已经被处理过了。你必须在自己的公共方法中检查这种状态标志并抛出ObjectDisposed异常(如果某个对象被处理过之后再次被调用的话)。

    4.禁止终结操作(finalization)。调用GC.SuppressFinalize(this)来完成这种事务。

     Finalization(终结器)原理:

     应用程序创建一个新对象时,new操作符会从堆中分配内存。如果这个对象定义了Finalize方法,那么该类型的实例在构造器被调用之前,会将指向该对象的一个指针放到一个finalization list中。finalization list是由GC控制的一个内部数据结构。列表中的每一项都指向一个对象,在回收该对象的内存前,会调用他的Finalize方法。

    GC开始时,假定某些对象(如B,C,D对象)被判定位垃圾后,GC会扫描finalization list 以查找指向前述对象(如B,C,D对象)的指针。若发现finalization list有指针指向前述对象(如B,C,D对象)。finalization list会移除指向前述对象(如B,C,D对象)的指针,并把指针追加到Freachable队列。

    当垃圾回收器将对象的引用从finalization list移至freachable队列时,对象不再被视为垃圾,其内存不能被回收,即对象“复活”。

    然后,GC开始compact可回收内存,特殊的高优先级CLR线程专门负责清空freachable队列,并调用finalize方法。

    再次GC被调用时,会发现应用程序的根不再指向它,freachable队列也已经清空。所以,这些对象的内存会被直接回收。

    整个过程中,实现Finalization(终结器)的对象需要至少执行两次垃圾回收才能释放其所占内存。(假设对象代龄被提升,则可能多次GC才回收其内存)。

    规范的Dispose实现模式:

     1     public class ComplexCleanupBase : IDisposable
     2     {
     3         // some fields that require cleanup  
     4         private SafeHandle handle;
     5 
     6         private bool disposed = false; // to detect redundant calls
     7 
     8         public ComplexCleanupBase()
     9         {
    10             // allocate resources
    11         }
    12 
    13         protected virtual void Dispose(bool disposing)
    14         {
    15             if (!disposed)
    16             {
    17                 if (disposing)
    18                 {
    19                     if (handle != null)  
    20                         handle.Dispose();
    21                     // dispose-only, i.e. non-finalizable logic
    22                 }
    23 
    24                 // shared cleanup logic
    25                 disposed = true;
    26             }
    27         }
    28 
    29         public void Dispose()
    30         {
    31             Dispose(true);
    32             GC.SuppressFinalize(this);
    33         }
    34 
    35         ~ComplexCleanupBase()
    36         {
    37             Dispose(false);
    38         } 
    39     }

    了解上述后,来看个问题:

    a. 数据库连接,文件连接假如不手动dispose(),资源会被回收么?

    回答上面这个问题前,先做个试验,

     1  protected void Button1_Click(object sender, EventArgs e)
     2         {
     3             byte[] b = new byte[] { 1, 2, 3, 4, 5 };
     4             FileStream fs = new FileStream(@"d:	emp.dat", FileMode.Create);
     5             fs.Write(b, 0, b.Length);
     6 
     7         }
     8 
     9         protected void Button2_Click(object sender, EventArgs e)
    10         {
    11             GC.Collect();          
    12         }
    13 
    14         protected void Button3_Click(object sender, EventArgs e)
    15         {
    16             File.Delete(@"d:	emp.dat");
    17         }

    Button1,Button2,Button3来回点几下,看看会有什么现象?

    连续点击Button1或先点Button1后点Buttion3,第二次都会报错,说明文件连接没有被释放。但是如果点击Button1再点击Button2再点击Button1那么就不会报错了,因为Button2垃圾回收将文件连接关闭了。  

    这么说来,数据库连接,文件连接假如不手动dispose(),资源也会被GC回收,因为FileStream里的SaveFileHandle实现了Finalize,执行GC,其Finalize方法起了作用。

    好强大的GC哦! 只不过,需要等待GC.collect()才能释放这些资源连接,但是呢这些资源开着开销很大很昂贵,所以推荐用完即手动dispose().

    这样看规范的Dispose实现模式,能够确保.net资源即使被遗忘关闭,借助垃圾回收机制,也能顺利清理其资源。

    好了,有了上面这些基础之后,再来看一个例子:

     1  public class Foo
     2     {
     3         Timer _timer;
     4 
     5         public Foo()
     6         {
     7             _timer = new Timer(1000);
     8             _timer.Elapsed += _timer_Elapsed;
     9             _timer.Start();
    10         }
    11 
    12         void _timer_Elapsed(object sender, ElapsedEventArgs e)
    13         {
    14             Console.WriteLine("Tick");
    15         }
    16     }
    17     class Program
    18     {
    19         static void Main(string[] args)
    20         {
    21             Foo foo = new Foo();
    22             foo = null; 
    23             Console.ReadLine();
    24         }
    25     }

    注:Timer类来自using System.Timers;
    执行发现会出现一连串的 “Tick”。 这里foo=null并未起到作用,Timer资源并未关闭。这里我们不深究为何foo=null不起作用,实际上是因为.net编译做了优化处理,foo=null直接被编译器忽视了。

    那怎么办才能做到万事顺利地关闭Timer?

    Dispose模式登场,修改后的类如下:

     1   public class Foo : IDisposable
     2     {
     3         Timer _timer;
     4 
     5         public Foo()
     6         {
     7             _timer = new Timer(1000);
     8             _timer.Elapsed += _timer_Elapsed;
     9             _timer.Start();
    10         }
    11 
    12         void _timer_Elapsed(object sender, ElapsedEventArgs e)
    13         {
    14             Console.WriteLine("Tick");
    15         }
    16 
    17         public void Dispose()
    18         {
    19             _timer.Dispose();
    20         }
    21         ~Foo()
    22         {
    23             Dispose();
    24         }
    25     }
    26     class Program
    27     {
    28         static void Main(string[] args)
    29         {
    30             using (Foo foo = new Foo())
    31             {
    32                 System.Threading.Thread.Sleep(3000);
    33             }
    34             Console.ReadLine();
    35         }
    36     }
  • 相关阅读:
    Net设计模式实例之适配器模式(Adapter Pattern)
    Net设计模式实例之单例模式( Singleton Pattern)
    Net设计模式实例之原型模式( Prototype Pattern)
    20个非常棒的Jquery实用工具
    Net设计模式实例之备忘录模式(Memento Pattern)
    10个免费的javascript富文本编辑器(jQuery and nonjQuery)
    <推荐>65个以自然风光为背景的UI设计
    Net设计模式实例之代理模式(Proxy Pattern)
    UML建模之状态图(Statechart Diagram)
    13个绚丽的Jquery 界面设计
  • 原文地址:https://www.cnblogs.com/startpoint/p/4203277.html
Copyright © 2011-2022 走看看