.NET深入学习笔记(3):垃圾回收与内存管理

简介:


   今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个《WCF分布式开发必备知识》系列后的一次休息吧。以前被别人面试的时候问过我GC工作原理的问题,我现在面试新人的时候偶尔也会问相关的问题。那么你是否也遇到这样的问题呢?比如你清楚.Net的垃圾回收机制吗?你能简述一下GC的工作原理吗?怎么样才能有效的管理内存呢?Using语句体内实例化的对象有什么作用?等等相关问题。下面我们就来详细讨论一下。相信你看完以后也可以面试别人。

     本节的组织如下,1..Net的类型和内存分配2.GC垃圾收集器的工作原理3.什么是非托管资源4.如何有效释放对象资源。总结.现在开始我们本节的学习。
    1..Net的类型和内存分配
     Net中 的所有类型都是(直接或间接)从 System.Object 类型派生的。
     CTS 中的类型被分成两大类——引用类型( reference type ,又叫托管类型 [managed type] ),分配在内存堆上,值类型( value type )。值类型分配在堆栈上。如图
    值类型在栈里,先进后出,值类型变量的生命有先后顺序,这个确保了值类型变量在推出作用域以前会释放资源。比引用类型更简单和高效。堆栈是从高地址往低地址分配内存。
     引用类型分配在托管堆(Managed Heap)上,声明一个变量在栈上保存,当使用new创建对象时,会把对象的地址存储在这个变量里。托管堆相反,从低地址往高地址分配内存,如图
2.GC垃圾收集器的工作原理
      上图中,当dataSet使用过期以后,我们不显示销毁对象,堆上的对象还继续存在,等待GC的 回收。
垃圾收集器通过分代支持对象的年龄化是推荐的但不是必需的。一代在内存里是一个具有相对年龄的对象的单位。对象的
代号或年龄标识对象属于那个分代。在应用程序的生命周期里,越近创建的对象属于越新的代,并且比早创建的对象具有
较低的分代号。最近分代里的对象代号是0.
      在new对象时,要先搜索空闲链表,找到最适合内存块,分配,调整内存块链表,合并碎片。new操作几乎可以在O(1)的时间完成,把堆顶指针加1。工作原理是: 当托管堆上剩余空间不足,或者Generator 0 的空间已满的时候GC运行,开始回收内存。垃圾回收的开始,GC对堆内存的压缩调整,对象集中到顶部。GC在扫描垃圾的时候会占用一定的CPU时间片的,最初的GC算法真的是扫描整个堆,效率低。现在的GC把堆中的对象分成3代,最 进入堆的是第0代(generation 0), 其次是generation 1, generation2. 第一次GC只扫描第0代。如果回收的空间足够当前使用就不必扫描其它generation的对象。所以,GC创建对象的效率比C++高效,不需要扫描全部堆空间。它通过扫描策略,再加上内存管理策略带来的性能提升,足以补偿GC所占用的CPU时间。
    3.什么是非托管资源
  常见的非托管资源就是包装操作系统资源的对象,例如文件,窗口或网络连接,对于这类资源虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它知道如何清理这些资源。好在.net Framework提供的Finalize()方法,它允许在垃圾回收器回收该类资源前,适当的清理非托管资源。这里列举几种常见的非托管资源:画笔、流对象、组件对象等等资源(Object,OdbcDataReader,OleDBDataReader,Pen,Regex,Socket,StreamWriter,ApplicationContext,Brush,
Component,ComponentDesigner,Container,Context,Cursor,FileStream,
Font,Icon,Image,Matrix,Timer,Tooltip)。(参考MSDN)
    4.如何有效释放非托管资源。
     GC无法管理非托管资源,那么如何释放非托管资源呢?.Net提供了两种方式:
(1)析构函数:垃圾收集器回收非托管对象的资源时,会调用对象的终结方法Finalize(),进行资源的清理工作,但是由于GC工作规则的限制,GC调用对象的Finalize方法,第一次不会释放资源,第二次调用之后才删除对象。
(2)继承 IDisposable接口,实现Dispose()方法,IDisposable接口定义了一个模式(具有语言级的支持),为释放未托管的资源提供了确定的机制,并避免产生析构函数固有的与垃圾收集器相关的问题。
   为了更好的理解垃圾回收机制,我特地写了部分代码,里面添加了详细的注释。定义单个类 FrankClassWithDispose(继承接口IDisposableFrankClassNoFinalize(没终结器)FrankClassWithDestructor(定义了析构函数)。
具体代码如下:
1using System; 
 2using System.Collections.Generic; 
 3using System.Text; 
 4using System.Data; 
 5using System.Data.Odbc; 
 6using System.Drawing; 
 7 //Coded By Frank Xu Lei 18/2/2009 
 8 //Study the .NET Memory Management 
 9 //Garbage Collector 垃圾收集器。可以根据策略在需要的时候回收托管资源, 
10 //但是GC不知道如何管理非托管资源。如网络连接、数据库连接、画笔、组件等 
11 //两个机制来解决非托管资源的释放问题。析构函数、IDispose接口 
12 //COM引用计数 
13 //C++手动管理,New Delete 
14 //VB自动管理 
15namespace MemoryManagement 
16{ 
17         //继承接口IDisposable,实现Dispose方法,可以释放FrankClassDispose的实例资源 
18         public  class FrankClassWithDispose : IDisposable 
19        { 
20                 private OdbcConnection _odbcConnection =  null
21                 
22                 //构造函数 
23                 public FrankClassWithDispose() 
24                { 
25                         if (_odbcConnection ==  null
26                                _odbcConnection =  new OdbcConnection(); 
27                        Console.WriteLine( "FrankClassWithDispose has been created "); 
28                } 
29                 //测试方法 
30                 public  void DoSomething() 
31                { 
32 
33                        /** /////code here to do something 
34                         return ; 
35                } 
36                 //实现Dispose,释放本类使用的资源 
37                 public  void Dispose() 
38                { 
39                         if (_odbcConnection !=  null
40                                _odbcConnection.Dispose(); 
41                        Console.WriteLine( "FrankClassWithDispose has been disposed"); 
42                } 
43        } 
44         //没有实现Finalize,等着GC回收FrankClassFinalize的实例资源,GC运行时候直接回收 
45         public  class FrankClassNoFinalize 
46        { 
47                 private OdbcConnection _odbcConnection =  null
48                 //构造函数 
49                 public FrankClassNoFinalize() 
50                { 
51                         if (_odbcConnection ==  null
52                                _odbcConnection =  new OdbcConnection(); 
53                        Console.WriteLine( "FrankClassNoFinalize    has been created"); 
54                } 
55                 //测试方法 
56                 public  void DoSomething() 
57                { 
58 
59                         //GC.Collect(); 
60                        /** /////code here to do something 
61                         return ; 
62                } 
63        } 
64         //实现析构函数,编译为Finalize方法,调用对象的析构函数 
65         //GC运行时,两次调用,第一次没释放资源,第二次才释放 
66         //FrankClassDestructor的实例资源 
67         //CLR使用独立的线程来执行对象的Finalize方法,频繁调用会使性能下降 
68         public  class FrankClassWithDestructor 
69        { 
70                 private OdbcConnection _odbcConnection =  null
71                 //构造函数 
72                 public FrankClassWithDestructor() 
73                { 
74                         if (_odbcConnection ==  null
75                                _odbcConnection =  new OdbcConnection(); 
76                        Console.WriteLine( "FrankClassWithDestructor    has been created"); 
77                } 
78                 //测试方法 
79                 public  void DoSomething() 
80                { 
81                        /** /////code here to do something 
82 
83                         return ; 
84                } 
85                 //析构函数,释放未托管资源 
86                ~FrankClassWithDestructor() 
87                { 
88                         if (_odbcConnection !=  null
89                                _odbcConnection.Dispose(); 
90                        Console.WriteLine( "FrankClassWithDestructor    has been disposed"); 
91                } 
92        } 
93} 
94 
 
其中使用了非托管的对象 OdbcConnection的实例。建立的客户端进行了简单的测试。客户端代码如下:
1using System; 
 2using System.Collections.Generic; 
 3using System.Text; 
 4using System.Data; 
 5using MemoryManagement; 
 6 //Coded By Frank Xu Lei 18/2/2009 
 7 //Study the .NET Memory Management 
 8 //Test The Unmanaged Objects Reclaimed. 
 9 //针对非托管代码的测试,比较 
10 //托管代码,GC可以更具策略自己回收,也可以实现IDisposable,调用Dispose()方法,主动释放。 
11namespace MemoryManagementClient 
12{ 
13         class Program 
14        { 
15                 static  void Main( string[] args) 
16                { 
17 
18                        /** //////////////////////////////////////////(1)//////////////////////////////////////////// 
19                         //调用Dispose()方法,主动释放。资源,灵活 
20                        FrankClassWithDispose _frankClassWithDispose =  null
21                         try 
22                        { 
23                                _frankClassWithDispose =  new FrankClassWithDispose(); 
24                                _frankClassWithDispose.DoSomething(); 
25                                 
26                        } 
27                         finally 
28                        { 
29                                 if (_frankClassWithDispose!= null
30                                _frankClassWithDispose.Dispose(); 
31                                 //Console.WriteLine("FrankClassWithDispose实例已经被释放"); 
32                        } 
33                                 
34                        /** //////////////////////////////////////////(2)////////////////////////////////////////////// 
35                         //可以使用Using语句创建非托管对象,方法执行结束前,会调用 
36                         using (FrankClassWithDispose _frankClassWithDispose2 =  new FrankClassWithDispose()) 
37                        { 
38                                 //_frankClassWithDispose2.DoSomething(); 
39                        } 
40 
41                        /** //////////////////////////////////////////(3)//////////////////////////////////////////// 
42                         //垃圾收集器运行的时候,一次就释放资源 
43                        FrankClassNoFinalize _frankClassNoFinalize =  new FrankClassNoFinalize(); 
44                        _frankClassNoFinalize.DoSomething(); 
45                            
46                        /** ///////////////////////////////////////////(4)////////////////////////////////////////////// 
47                         //垃圾收集器运行的时候,两次才能够释放资源 
48                        FrankClassWithDestructor _frankClassWithDestructor =  new FrankClassWithDestructor(); 
49                        _frankClassWithDestructor.DoSomething(); 
50                        /** ////////////////////////////////////////////(5)///////////////////////////////////////////// 
51                         //不能使用Using语句来创建对象,因为其没实现IDispose接口 
52                         //using (FrankClassWithDestructor _frankClassWithDestructor2 = new FrankClassWithDestructor()) 
53                         //{ 
54                         //        _frankClassWithDestructor2.DoSomething(); 
55                         //} 
56 
57                        /** /////////////////////////////////////////////////////////////////////////////////////// 
58                         //For Debug 
59                        Console.WriteLine( "Press any key to continue"); 
60                        Console.ReadLine(); 
61 
62                 
63                } 
64        } 
65} 
66 
有些时候资源必须在特定时间释放,类可以实现执行资源管理和清除任务方法IDisposable.Dispose的接口IDisposable。
如果调用者需要调用Dispose方法清理对象,类作为契约的一部分必须实现Dispose方法。垃圾收集器默认情况下不会调用
Dispose方法;然而,实现Dispose方法可以调用GC里的方法去规范垃圾收器的终结行为。
值得一提的是: 调用Dispose()方法,主动释放资源,灵活,可以使用Using语句创建非托管对象,方法执行结束前,会调用
Dispose()方法释放资源,
这两端代码的效果是一样的,可以查看编译后IL。
1. try 
 2    { 
 3        IL_0003:    nop 
 4        IL_0004:    newobj         instance  void [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor() 
 5        IL_0009:    stloc.0 
 6        IL_000a:    ldloc.0 
 7        IL_000b:    callvirt     instance  void [MemoryManagement]MemoryManagement.FrankClassWithDispose::DoSomething() 
 8        IL_0010:    nop 
 9        IL_0011:    nop 
10        IL_0012:    leave.s        IL_0028 
11    }     // end .try 
12     finally 
13    { 
14        IL_0014:    nop 
15        IL_0015:    ldloc.0 
16        IL_0016:    ldnull 
17        IL_0017:    ceq 
18        IL_0019:    stloc.s        CS$4$0000 
19        IL_001b:    ldloc.s        CS$4$0000 
20        IL_001d:    brtrue.s     IL_0026 
21        IL_001f:    ldloc.0 
22        IL_0020:    callvirt     instance  void [MemoryManagement]MemoryManagement.FrankClassWithDispose::Dispose() 
23        IL_0025:    nop 
24        IL_0026:    nop 
25        IL_0027:    endfinally 
26    }     // end handler 
27    IL_0028:    nop 
28    IL_0029:    newobj         instance  void [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor() 
29    IL_002e:    stloc.1 
30    . try 
31    { 
32        IL_002f:    nop 
33        IL_0030:    nop 
34        IL_0031:    leave.s        IL_0045 
35    }     // end .try 
36     finally 
37    { 
38        IL_0033:    ldloc.1 
39        IL_0034:    ldnull 
40        IL_0035:    ceq 
41        IL_0037:    stloc.s        CS$4$0000 
42        IL_0039:    ldloc.s        CS$4$0000 
43        IL_003b:    brtrue.s     IL_0044 
44        IL_003d:    ldloc.1 
45        IL_003e:    callvirt     instance  void [mscorlib]System.IDisposable::Dispose() 
46        IL_0043:    nop 
47        IL_0044:    endfinally 
48    }     // end handler 
49
Using 语句有同样的效果,来实现非托管对象资源的释放。这点在面试中也会经常遇到,Using关键字的用法有哪几种等等类似的问题。基本理想的答案都是除了引用命名空间,和命名空间设置别名外,就是这个用法实现如try finally块一样作用的对非托管对象资源的回收。只是一种简便的写法。
     当你用Dispose方法释放未托管对象的时候,应该调用GC.SuppressFinalize。如果对象正在终结队列(finalization queue),GC.SuppressFinalize会阻止GC调用Finalize方法。因为Finalize方法的调用会牺牲部分性能。如果你的Dispose方法已经对委托管资源作了清理,就没必要让GC再调用对象的Finalize方法(MSDN)。附上MSDN的代码, 大家可以参考.
public  class BaseResource: IDisposable 

      // 指向外部非托管资源 
      private IntPtr handle; 
      // 此类使用的其它托管资源. 
      private Component Components; 
      // 跟踪是否调用.Dispose方法,标识位,控制垃圾收集器的行为 
      private  bool disposed =  false
 
      // 构造函数 
      public BaseResource() 
     { 
             // Insert appropriate constructor code here. 
     } 
 
      // 实现接口IDisposable. 
      // 不能声明为虚方法virtual. 
      // 子类不能重写这个方法. 
      public  void Dispose() 
     { 
            Dispose( true); 
             // 离开终结队列Finalization queue    
             // 设置对象的阻止终结器代码 
             //    
            GC.SuppressFinalize( this); 
     } 
 
      // Dispose(bool disposing) 执行分两种不同的情况. 
      // 如果disposing 等于 true, 方法已经被调用 
      // 或者间接被用户代码调用. 托管和非托管的代码都能被释放 
      // 如果disposing 等于false, 方法已经被终结器 finalizer 从内部调用过, 
      //你就不能在引用其他对象,只有非托管资源可以被释放。 
      protected  virtual  void Dispose( bool disposing) 
     { 
             // 检查Dispose 是否被调用过. 
             if(! this.disposed) 
            { 
                  // 如果等于true, 释放所有托管和非托管资源    
                  if(disposing) 
                 { 
                         // 释放托管资源. 
                        Components.Dispose(); 
                 } 
                  // 释放非托管资源,如果disposing为 false,    
                  // 只会执行下面的代码. 
                 CloseHandle(handle); 
                 handle = IntPtr.Zero; 
                  // 注意这里是非线程安全的. 
                  // 在托管资源释放以后可以启动其它线程销毁对象, 
                  // 但是在disposed标记设置为true前 
                  // 如果线程安全是必须的,客户端必须实现。 
 
            } 
            disposed =  true;                    
     } 
                 // 使用interop 调用方法    
                 // 清除非托管资源. 
                [System.Runtime.InteropServices.DllImport( "Kernel32")] 
                 private  extern  static Boolean CloseHandle(IntPtr handle); 
 
      // 使用C# 析构函数来实现终结器代码 
      // 这个只在Dispose方法没被调用的前提下,才能调用执行。 
      // 如果你给基类终结的机会. 
      // 不要给子类提供析构函数. 
     ~BaseResource()             
     { 
             // 不要重复创建清理的代码. 
             // 基于可靠性和可维护性考虑,调用Dispose(false) 是最佳的方式 
            Dispose( false); 
     } 
 
      // 允许你多次调用Dispose方法, 
      // 但是会抛出异常如果对象已经释放。 
      // 不论你什么时间处理对象都会核查对象的是否释放,    
      // check to see if it has been disposed. 
      public  void DoSomething() 
     { 
             if( this.disposed) 
            { 
                  throw  new ObjectDisposedException(); 
            } 
     } 
     对于需要调用Close方法比Dispose方法更加自然的类型,可以在    基类增加一个Close方法。 
    Close方法无参调用执行恰当清理工作的Dispose方法。 
    下面的例子演示了Close方法。 
      // 不要设置方法为virtual. 
      // 继承类不允许重写这个方法 
      public  void Close() 
     { 
      // 无参数调用Dispose参数. 
             Dispose(); 
     } 
 
      public  static  void Main() 
     { 
                 // Insert code here to create 
                 // and use a BaseResource object. 
     } 
}
总结:看了本文以后,不知对你是否有所帮助,如果你理解了.net垃圾回收的机制和GC的工作原理,以及包含如何管理非托管资源,你就会成为一个内存管理的高手。如果面试官问道这个问题,你就可以详细阐述你对这类问题的理解和看法。希望这篇文章能对你的工作和学习带来帮助~



 本文转自 frankxulei 51CTO博客,原文链接:http://blog.51cto.com/frankxulei/318541,如需转载请自行联系原作者


相关文章
|
1月前
|
JavaScript 前端开发 Java
垃圾回收机制会导致内存泄漏吗?
【10月更文挑战第29天】虽然JavaScript的垃圾回收机制本身是为了有效地管理内存,但开发者在编写代码时需要注意上述这些可能导致内存泄漏的情况,遵循良好的编程习惯,及时释放不再使用的资源,以确保程序能够高效地利用内存资源,避免出现内存泄漏问题。
|
26天前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
36 3
|
1月前
|
算法 Java 开发者
Java内存管理与垃圾回收机制深度剖析####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,特别是其垃圾回收机制的工作原理、算法及实践优化策略。不同于传统的摘要概述,本文将以一个虚拟的“城市环卫系统”为比喻,生动形象地揭示Java内存管理的奥秘,旨在帮助开发者更好地理解并调优Java应用的性能。 ####
|
1月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
52 6
|
1月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
24天前
|
存储 监控 算法
Java内存管理的艺术:深入理解垃圾回收机制####
本文将引领读者探索Java虚拟机(JVM)中垃圾回收的奥秘,解析其背后的算法原理,通过实例揭示调优策略,旨在提升Java开发者对内存管理能力的认知,优化应用程序性能。 ####
38 0
|
2月前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理方式,特别是垃圾回收机制。我们将了解Java的自动内存管理是如何工作的,它如何帮助开发者避免常见的内存泄漏问题。通过分析不同垃圾回收算法(如标记-清除、复制和标记-整理)以及JVM如何选择合适的垃圾回收策略,本文旨在帮助Java开发者更好地理解和优化应用程序的性能。
|
2月前
|
数据库连接 开发者
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第15天】在.NET中,有两种有效的资源释放方式:一是使用`using`语句,适用于实现`IDisposable`接口的对象,如文件流、数据库连接等,能确保资源及时释放,避免泄漏;二是手动调用`Dispose`方法并处理异常,提供更灵活的资源管理方式,适用于复杂场景。这两种方式都能有效管理资源,提高应用性能和稳定性。
|
2月前
|
算法 Java 数据库连接
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第14天】在 .NET 中,`IDisposable` 接口提供了一种标准机制来释放非托管资源,如文件句柄、数据库连接等。此类资源需手动释放以避免泄漏。实现 `IDisposable` 的类可通过 `Dispose` 方法释放资源。使用 `using` 语句可确保资源自动释放。此外,.NET 的垃圾回收器会自动回收托管对象所占内存,提高程序效率。示例代码展示了如何使用 `MyFileHandler` 类处理文件操作并释放 `FileStream` 资源。
|
2月前
|
监控 算法 Java
Java中的内存管理:理解垃圾回收机制
【10月更文挑战第2天】 在本文中,我们将深入探讨Java编程语言中的内存管理机制,特别是垃圾回收机制。我们将从基本原理、垃圾回收算法到实际应用场景全面解析,帮助你更好地理解和优化Java应用的内存使用。无论你是初学者还是有经验的开发者,这篇文章都能带给你新的启发和思考。
38 2

热门文章

最新文章