.NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配
在" .NET的堆和栈01,基本概念、值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配。我们知道:当执行一个方法的时候,值类型实例会在"栈"上分配内存,而引用类型实例会在"堆"上分配内存,当方法执行完毕,"栈"上的实例由操作系统自动释放,"堆"上的实例由.NET Framework的GC进行回收。
在" .NET的堆和栈02,值类型和引用类型参数传递以及内存分配"中,我们了解了值类型参数和引用类型参数在传递时的内存分配情况。
在" .NET的堆和栈03,引用类型对象拷贝以及内存分配"中,我们了解了在拷贝引用类型对象时的内存分配情况。
而本篇的重点要放在:对托管和非托管资源的垃圾回收、处理以及内存分配情况。
主要包括:
■ 什么样的对象被GC认为是垃圾?
■ GC如何回收?
□ GC对托管堆中对象的回收
□ GC对非托管堆中对象的回收、处理
※ 对资源的回收
○ 通过析构函数回收
○ 通过实现IDisposable接口回收
※ 对静态值类型变量的处理
※ 对静态引用类型变量的处理
■ GC何时回收?
■ GC回收之后,又执行哪些操作?
当运行时有新的引用对象产生,将会被放到托管堆中这组对象的最上面。
Sample { //析构函数 ~Sample() { } }
在托管堆中,那些带有析构函数的实例,将被放置到"Finalization Queue"中。
对于那些不被任何其它对象所引用,如果没有析构函数,比如2,将被直接回收,如果有析构函数,例如4,会被放到"Freachable Queue"中,等待GC实施下一轮回收。
当为一个类添加析构函数后,为GC增加了额外的工作,代价是比较昂贵的,更现实的做法是让类来实现IDisposable接口。
2、通过实现IDisposable接口回收
首先让一个类实现IDisposable接口。
public class ResourceClass : IDisposable { public void Dispose() { //TODO:实现回收逻辑 } }
在应用程序中调用如下实施回收。
using(ResourceClass re = new ResourceClass()) { }
对静态值类型变量的处理
class Counter { private static int s_Number = 0; public static int GetNextNumber() { int newNumber = s_Number; // DO SOME STUFF newNumber += 1; s_Number = newNumber; return newNumber; } }
如上,当方法有方法处理静态字段就需要注意了,2个线程同时调用GetNextNumber()会得到相同的结果,而我们的本意是:每调用一次方法,静态字段s_Number自增1。
我们可以在处理逻辑块中加锁,每次只允许一个线程执行。
class Counter { private static int s_Number = 0; public static int GetNextNumber() { lock (typeof(Counter)) { int newNumber = s_Number; // DO SOME STUFF newNumber += 1; s_Number = newNumber; return newNumber; } } }
对静态引用类型变量的处理
class Olympics { public static Collection<Runner> TryoutRunners; } class Runner { private string _fileName; private FileStream _fStream; public void GetStats() { FileInfo fInfo = new FileInfo(_fileName); _fStream = _fileName.OpenRead(); } }
Singleton模式可以很好地避免上述问题,它保证了在任何时候,内存中只存在某个类的一个实例。
public class Earth { private static Earth _instance = new Earth(); private Earth(){} public static Earth GetInstance(){return _instance;} }
以上,单例模式的必要构成要素包括:
1、私有静态引用类型变量
2、私有构造函数
3、获取类实例的静态方法
GC何时回收?
GC会周期性地执行垃圾回收、内存清理工作,以下情况会启动GC:
● 托管堆内存不足溢出时
● 调用GC.Collect()方法强制执行垃圾回收
● Windows报告内存不足
● CLR卸载AppDomain
GC回收之后,又执行哪些操作?
GC在垃圾回收之后,托管堆上将出现多个被收集对象的"空洞",为了避免托管堆的内存碎片,会重新分配内存、压缩托管堆。
参考资料:
C# Heap(ing) Vs Stack(ing) in .NET: Part IV
《你必须知道的.NET(第2版)》,作者王涛。
".NET的堆和栈"系列包括: