CLR自动管理内存---《clr via c#》笔记
2012-02-03 16:27 海不是蓝 阅读(649) 评论(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;