代码改变世界

CLR自动管理内存---《clr via c#》笔记

2012-02-03 16:27  海不是蓝  阅读(637)  评论(0编辑  收藏  举报

理解垃圾回收平台的基本工作原理

面向对象的环境中,每个类型都代表可供程序使用的一种资源。要使用这些资源,就需要为资源分配内存,当不使用资源的时候再回收销毁。过程如下。

 

1.调用IL指令newobj,为代表资源的类型分配内存,c#中就是new操作符。

2.初始化内存,设置资源为初始状态,c#中是类型的实例构造器负责初始化。

3.访问类型的成员(可以重复)使用资源。(就是你平时不断的使用一个对象的过程)

4.摧毁资源的状态以进行清理。

5.释放内存,垃圾回收器独自负责这一步。

 

Jeffrey大牛的抱怨

Jeffrey这里有开始回忆他以前写c++的光阴了!

Jeffrey抱怨:进行非托管编程的时候,内存管理不当是主要的bug。

Jeffrey抱怨:花了大量的时间去考虑内存的管理,极大的分散了他的精力。

 

但是CLR的垃圾回收来了!Jeffrey开心的笑了!

 

 

 

红领巾???


 

从托管堆分配资源

CLR要求所有资源都从托管堆分配!别想逆天!

 

这个托管堆和C语言中的堆相似,不同的地方是托管堆不需要你去手动删除里面的对象。程序不需要的对象它自己会删除!

 

托管堆如何知道应用程序不再用一个对象???

要想知道问题的答案?Jeffrey说:“亲,稍后告诉你!”。

从基本概念讲起:

CLR维护着一片连续的地址空间,最开始没有对应物理存储空间,这个地址就是托管堆。托管堆还有个指针,叫“NextObjPtr”,它指向下一个对象在堆中的分配位置。

 

Newobj指令导致clr执行的步骤

1.计算类型的字段需要的字节数。

2.加上对象的开销需要的字节数。每个对象有2个开销:一个是类型对象指针和一个同步索引块。

3.CLR检查保留区是否能提供分配对象所需的字节数。

这里有个彩蛋,托管堆中分配的对象内存地址是连续的,例如c语言这些从线程栈中分配对象的内存可能是分散的。

 

垃圾回收算法

垃圾回收器检查托管堆中是否有程序不用的对象,如果有,那个对象的内存将被回收。如果回收了一次,托管堆中还是没有可以使用的内存,那么new操作符将会抛出OutOfMemoryException异常!

 

然后Jeffrey开始解释什么是根!

然后他写了一个类,竟然搬上了32cpu下的80386汇编!

什么EBX,EDI这些东西,我早忘记了。直接不看了!

大概也就是从汇编角度去解释clr怎么进行回收的。

 

根和回收

每个程序都包含一组根,每个根都是一个存储位置,其中包含了指向对引用对象的一个指针!该指针要么是托管堆中一个对象,要么是null。

1.类型中的静态字段是一个根。

2.任何方法参数或局部变量也是一个根。

3.只有引用类型的变量才能被认为是根,值类型的变量永远不会是根。

 

接下来就是那万恶的汇编了!!!我怀着万分痛苦的心情还是看了下。

 

1.标记阶段

大概意思就是垃圾回收器检查托管堆中的每一个根,然后沿着这些根去检查根下面是否有引用其他对象,如果有,先标记好然后在根的同步索引字段上开启一位。再检查这个对象在堆下面的对象是否存在引用,

如果不存在,那么这个对象不会被标记。再去检查其他根。

 

2.压缩阶段

已经标记好的对象是可达的对象,未标记的对象是不可达的,也就是垃圾对象。

现在开始第二阶段:压缩阶段。

 

这个阶段中,垃圾回收器会线性的遍历堆,寻找未标记对象的连续内存块。

如果内存块比较小,垃圾回收器会直接无视他们,忽略掉。

如果比较大,垃圾回收器会把非垃圾的对象移动到这里以压缩堆。

 

垃圾回收会造成显著的性能损失!这是托管堆的主要缺点。垃圾回收只在第0代满的时候才发生。

 

垃圾回收与调试

只要对象变得不可达,就会成为垃圾回收器的目标!不保证对象在方法的生存周期中始终存活。

static void Main()

{

    Timer t = new Timer(TimerCallBack, null, 0, 2000);

    Console.Read();

}

private static void TimerCallBack(object obj)

{

    Console.WriteLine("TimerCallBack:{0}"DateTime.Now);

    GC.Collect();

}

上面的程序一般认为会每2秒输出一次,这里主动调用了GC回收方法。但是运行的时候发现回调方法只被调用了一次。

因为这里t初始化之后,垃圾回收器发现在main方法里面再也没有用过t。所以调用了一次回调方法以后t就被回收了!

 

有些开发人员尝试这样去解决

Timer t = new Timer(TimerCallBack, null, 0, 2000);

Console.Read();

t = null;

但是发现最后运行的情况和上面一样!

因为将局部变量设为null,等于没有引用这个变量。所以垃圾回收器一样的回收你!

 

正确的解决方法

Timer t = new Timer(TimerCallBack, null, 0, 2000);

Console.Read();

t.Dispose();

因为对象必须存活才能调用Dispose方法!

 

---------------------------------------------------------------------

Finalize和Safehandle就直接先忽略。以后接触到再看!

唉!手动监视和控制对象的生存期也先忽略吧!AppDomain和com操作不太熟悉,以后再看。

---------------------------------------------------------------------

 

代是CLR垃圾回收采用的一种机制,它的目的就是提升应用程序的性能。

第0代

托管堆在初始化时不包含任何对象,添加到堆的对象称为第0代对象。

第1代

当程序不断运行,不断有很多对象添加到托管堆,那么第0代预算的容量满了,那么GC就会开始一次垃圾回收,那些0代中不可达的对象就会被回收。

在垃圾回收中存活的对象就会升级为第1代。

第2代

当第1代中的对象越来越多,垃圾回收器就会考虑回收第0代和第1代中的垃圾对象,这次回收以后,存活的第1代对象就会升级到第2代。

 

下面写个程序演示代

Console.WriteLine("当前系统支持的最大代:{0}"GC.MaxGeneration);

//在堆中创建一个新的GenObj

Object obj = new object();

Console.WriteLine("obj Gen:{0}"GC.GetGeneration(obj));//0

GC.Collect();

Console.WriteLine("obj Gen:{0}"GC.GetGeneration(obj));//1

GC.Collect();

Console.WriteLine("obj Gen:{0}"GC.GetGeneration(obj));//2

GC.Collect();

Console.WriteLine("obj Gen:{0}"GC.GetGeneration(obj));//2(不能再大了)

obj = null;