.NET中的垃圾回收机制
在.NET中,应用程序的内存分成两个部分,一部分为托管内存,由.NET运行时(CLR)进行管理;另外一部分为非托管内存(通过Win32 API或者Marshal.AllocateXXX方法分配),需要程序员自己进行管理。托管内存都在堆(Heap)中进行分配,在这个堆中,.NET通过不同的“代”(Generation)来组织对象。托管堆共分成三代:
- 第0代(Generation 0):这一代是最年轻的,其中包含一些短生存期的对象,比如方法中的局部变量等。这一代的对象会被回收的比较频繁,在新建对象的时候,一般会创建在这一代中。
- 第1代(Generation 1):这一代中的对象也是短生存期的,但只是作为一个过渡。一些较大的对象,在创建的时候也会在这一代。
- 第2代(Generation 2):这一代中的对象都是长生存期的,比如静态类、静态成员等。他们被回收的概率会很低,所以在编写程序的时候应该尽量避免使用静态成员。
在每一代中的执行(通过显式调用GC.Collect方法)一次内存回收时,如果有对象无法被回收,它们将被移到下一代中。比如第1代中定义了一个对象,它在一次回收操作中无法被回收,那它就会被移到第1代中去。这样的好处就是移动之后的对象下次不会再被频繁的尝试去回收。因为越是后代中的对象,被回收的频率就越低。
下面通过一个非常简单的程序来演示内存回收,它主要的功能是通过一个循环不断地创建一个数组,我们主要研究这些数组所占用的内存在何时会被回收。代码中的Sleep语句主要是为了让结果显示的明显一些,否则因为时间间隔太小,而看不清楚内存的变化。接下去的测试都是基于下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { int N = 10; float[] x = null; for (int i = 0; i < N; i++) { Console.WriteLine(i.ToString()); x = new float[1000000]; //Thread.Sleep(1000); //GC.Collect(); } x = null; } } } |
第一次尝试,数组大小为1000,未开启Collect:
上图纵坐标为程序占用的内存数(字节);横坐标是执行时间(秒)(下同)。可以看到内存一直在增加,并没有降低过。
第二次尝试,数组大小为1000,开启Collect:
我们可以看到,每隔一秒钟(就是我们Sleep的时间),内存就会下降一块。
第三次尝试,数组大小为1000000,未开启Collect:
结果与第二次尝试类似,因为每次创建的数组大小非常大,已经达到垃圾回收器内置的阈值,所以会自动执行回收。
垃圾回收器的工作机制比较复杂,已经不在本文的讨论范围内了,有兴趣的同学可以参考MSDN中的相关文章。推荐下面一片文章,介绍了GC的基本知识以及一些性能提升点:
http://msdn.microsoft.com/en-us/library/ms973837.aspx
Aug16
August 16, 2012 at 5:44 am
总算更新了。。。
August 16, 2012 at 6:13 am
哈哈,月刊了
August 16, 2012 at 6:13 am
其实有很多草稿,就是懒得去整理