今天抽空来讨论一下.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(继承接口IDisposable)、FrankClassNoFinalize(没终结器)、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的工作原理,以及包含如何管理非托管资源,你就会成为一个内存管理的高手。如果面试官问道这个问题,你就可以详细阐述你对这类问题的理解和看法。希望这篇文章能对你的工作和学习带来帮助~

深入了解C#系列:谈谈C#中垃圾回收与内存管理机制的更多相关文章

  1. C#中垃圾回收与内存管理机制

    今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个<WCF分布式开发必备知识>系列后的一次休息吧.以前被别人面试的时候问过我GC工作原理的问题,我现在面试新人的时候偶尔也会 ...

  2. JavaScript 之垃圾回收和内存管理

    JavaScript 具有自动垃圾收集机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存.而在 C 和 C++ 之类的语言中,开发人员的一项基本 ...

  3. 《JavaScript 闯关记》之垃圾回收和内存管理

    JavaScript 具有自动垃圾收集机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存.而在 C 和 C++ 之类的语言中,开发人员的一项基本 ...

  4. C++中的垃圾回收和内存管理

    最开始的时候看到了许式伟的内存管理变革系列,看到性能测试结果的时候,觉得这个实现很不错,没有深入研究其实现.现在想把这个用到自己的一个项目中来,在linux下编译存在一些问题,所以打算深入研究一下. ...

  5. C++中的垃圾回收和内存管理(续)

    boost memory的gc_allocator的使用 首先编译生成boost-memory的库,由于生成的是.so的动态库,所以需要在运行程序之前,将库文件的路径添加到LD_LIBRARY_PAT ...

  6. 【java虚拟机序列】java中的垃圾回收与内存分配策略

    在[java虚拟机系列]java虚拟机系列之JVM总述中我们已经详细讲解过java中的内存模型,了解了关于JVM中内存管理的基本知识,接下来本博客将带领大家了解java中的垃圾回收与内存分配策略. 垃 ...

  7. python中垃圾回收机制

    Python垃圾回收机制详解   一.垃圾回收机制 Python中的垃圾回收是以引用计数为主,分代收集为辅.引用计数的缺陷是循环引用的问题.在Python中,如果一个对象的引用数为0,Python虚拟 ...

  8. 深入理解Node.js中的垃圾回收和内存泄漏的捕获

    深入理解Node.js中的垃圾回收和内存泄漏的捕获 文章来自:http://wwsun.github.io/posts/understanding-nodejs-gc.html Jan 5, 2016 ...

  9. php中垃圾回收机制

    php中垃圾回收机制 我们可能在开发中经常会听到gc,是的gc就是垃圾回收容器,全称Garbage Collection. 此篇文章中“垃圾”的概念:如果一个变量容器能被减少到0,说明他就已经没有被引 ...

随机推荐

  1. YARN资源调度器

    YARN资源调度器 转载请注明出处:http://www.cnblogs.com/BYRans/ 概述 集群资源是非常有限的,在多用户.多任务环境下,需要有一个协调者,来保证在有限资源或业务约束下有序 ...

  2. 解决Android Graphical Layout 界面效果不显示

    解决Android Graphical Layout 界面效果不显示 qq463431476

  3. linux下遇见mysql启动报2002错误解决办法

    前言:目前问题解决了,但是仍不知道是什么原因造成的,在出现问题前安装uWSGI后,mysql就出现这个问题的,哪位大侠说说这是怎么回事? 正文:Linux 下 Mysql error 2002 错误解 ...

  4. POJ 2225 / ZOJ 1438 / UVA 1438 Asteroids --三维凸包,求多面体重心

    题意: 两个凸多面体,可以任意摆放,最多贴着,问他们重心的最短距离. 解法: 由于给出的是凸多面体,先构出两个三维凸包,再求其重心,求重心仿照求三角形重心的方式,然后再求两个多面体的重心到每个多面体的 ...

  5. Slam(即时定位与地图构建) 知识篇

    Slam即时定位与地图构建 技术解释 同步定位与地图构建(SLAM或Simultaneous localization and mapping)是一种概念:希望机器人从未知环境的未知地点出发,在运动过 ...

  6. 操作系统IO模型

    操作系统IO模型 声明:如下内容是根据APUE和mycat两本著作中关于I/O模式的一些内容加上自己的一些理解整理而成,仅供学习使用. 本节内容 UNIX下可用的五种I/O模型 三种I/O模型 Rea ...

  7. 对 Python 语法不够了解导致的 bug

    对 Python 语法不够了解导致的 bug. `in` '20' in '11264,6144,4096,3072,2048,1024,300,30' Out[7]: True a_list = ' ...

  8. PyCharm3.0默认快捷键(翻译的)

    PyCharm3.0默认快捷键(翻译的) PyCharm Default Keymap 1.编辑(Editing) Ctrl + Space    基本的代码完成(类.方法.属性)Ctrl + Alt ...

  9. Android利用Jsoup解析html 开发网站客户端小记。

    这些天业余时间比较多,闲来无事,想起了以前看过开发任意网站客户端的一篇文章,就是利用jsoup解析网站网页,通过标签获取想要的内容.好了废话不多说,用到的工具为 jsoup-1.7.2.jar包,具体 ...

  10. ECMAScript 5(ES5)中bind方法简介备忘

    一直以来对和this有关的东西模糊不清,譬如call.apply等等.这次看到一个和bind有关的笔试题,故记此文以备忘. bind和call以及apply一样,都是可以改变上下文的this指向的.不 ...