托管堆基础
在面向对象的环境中,每个类型都代表可供程序使用的一种资源。要使用这些资源,就必须为代表资源的类型分配内存。包含以下步骤:
- 调用IL指令newobj,为代表资源的类型分配内存(一般使用C# new操作符来完成)。
- 初始化内存,设置资源的初始状态并使资源可用。类型的实例构造器负责设置初始状态。
- 访问类型的成员来使用资源(有必要可以重复)。
- 摧毁资源的状态以进行清理。
- 释放内存。垃圾回收器独自负责这一步。
CLR要求所有对象都从托管堆分配。
托管堆
进程初始化时,CLR划出一个地址空间区域作为托管堆。CLR还要维护一个指针,我们把它称作NextObjPtr。该指针指向下一个对象在堆中的分配位置。刚开始的时候,NextObjPtr设为地址空间区域的基地址。
C#的new操作符导致CLR执行以下步骤
- 计算类型的字段(以及从基类型继承的字段)所需的字节数
- 加上对象的开销所需的字节数。
对象开销:类型对象指针和同步块索引。32位应用程序各自需要32位共64位,即8字节。64位应用程序各自需要64位,即16字节。
- CLR检查区域中是否有分配对象所需的字节数。如果有足够的可用空间,就在NextObjPtr指针指向的地址处放入对象,为对象分配的字节会被清零。接着调用类型的构造器(为this参数传递NextObjPtr),new 操作符返回对象引用。就在返回这个引用之前,NextObjPtr指针的值会加上对象占用的字节数来得到一个新值,即下一个对象放入托管堆时的地址。
垃圾回收算法
引用计数算法(COM,Component Object Model),引用跟踪算法(CLR)。
引用跟踪算法只关心引用类型的变量,因为只有这种变量才能引用堆上的对象;值类型变量直接包含值类型实例。引用类型变量可在许多场合使用,包括类的静态和实例字段,或者方法的参数和局部变量。我们将所有引用类型的变量都称为根。
GC步骤
- 开始GC时,首先暂停进程中的所有线程。这样可以防止线程在CLR检查期间访问对象并更改其状态。
- CLR进入GC的标记阶段。CLR遍历堆中的所有对象,将同步块索引字段中的一位设为0.这表明所有对象都应该删除。
- 然后,CLR检查所有活动根,查看它们引用了哪些对象。这正是CLR的GC称为引用跟踪GC的原因。如果一个根包含null,CLR忽略这个根并继续检查下个根。任何根如果引用了堆上的对象,CLR都会标记那个对象,也就是将该对象的同步块索引中的位设为1.一个对象被标记后,CLR会检查那个对象中的根,标记它们引用的对象。如果发现对象已经标记,就不会重新检查对象的字段。这就避免了因为循环引用而产生死循环。
检查完毕后,堆中的对象要么已标记,要么未标记。已标记的对象不能被垃圾回收,因为至少有一个根在引用它。我们说这种对象的可达(reachable)的,未标记的对象是不可达(unreachable)的。
- 进入GC的压缩(compact)阶段。压缩所有幸存下来的对象,使它们占用连续的内存空间。作为压缩阶段的一部分,CLR还要从每个根减去所引用的对象在内存中偏移的字节数。这样就能保证每个根还是引用和之前一样的对象;只是对象在内存中变换了位置。
压缩好内存后,托管堆的NextObjPtr指针指向最后一个幸存对象之后的位置。下一个分配的对象将放到这个位置。
重要提示
静态字段引用的对象一直存在,知道用于加载类型的AppDomain卸载为止。内存泄漏的一个常见原因就是让静态字段引用某个集合对象,然后不停的向集合添加数据项。静态字段使集合一直存活,而集合对象使所有数据项一直存活。因此应尽量避免使用静态字段。
垃圾回收和调试
static void Main(string[] args)
{
//初始化变量t后,如果后续没有变量引用t的实例,则在强制垃圾回收时会回收t的实例。
//Timer t = new Timer(TimerCallback, null, 0, 2000);
//Timer t = null;
//t = new Timer(TimerCallback, null, 0, 2000);//两种初始化方式一样。
Timer t = new Timer(TimerCallback);
t.Change(0, 2000);//执行初始化后,再执行调用则在发生垃圾回收的时候不会回收t的实例。
//Timer t2 = t;
Console.ReadKey();
//t.Dispose();
}
private static void TimerCallback(object state)
{
Console.WriteLine(DateTime.Now);
GC.Collect();
}
代:提升性能
CLR的GC是基于代的垃圾回收器(generational garbage collector),它对你的代码做出了以下几点假设。
- 对象越新,生存期越短。
- 对象越老,生存期越长。
- 回收堆的一部分,速度快于回收整个堆。
代的工作原理。。。。待补充。
垃圾回收触发条件
- 代码显式调用System.GC的静态方法Collect方法
- Windows报告低内存情况
- CLR正在卸载AppDomain
- CLR正在关闭