GC的实现原理及应用

c#的垃圾回收原理详解及实际项目应用

Posted by Luffy on April 20, 2017

前言

考虑下面一个问题 (引用自StackOverFlow):

//A,B recycle reference
public class A
{
	B b;
	public A(B b)
	{
		this.b = b;
	}
}

public class B
{
     A a;
     public B() 
     { 
     	this.a =  new A(this);
     }
}

类A与B互相引用,那么CLR能够正常的进行GC么? 要回答这个问题,就需要了解GC的运作原理。

GC原理

参考自此篇Blog。 GC,即garbage collection,是CLR提供的一种提供自动内存管理服务的机制。有了GC之后,可以不必手动管理内存的释放(参考c++中delete),减少内存泄漏等问题。更详细的解释和GC的优缺点可查看Wiki相关网页

实现GC有许多算法(Reference Counting,Mark Sweep等)。c#所采取的标记压缩法(Mark-Compact),基本原理是:以应用程序的root为基础,遍历所有托管物体,标记能不能被搜索到的物体(Mark)并清除;然后对内存进行压缩(Compact)。如下图所示:

GC示意图

关于GC root,可参考自这篇文章 。 大体可分为以下几类:

  • 当前运行方法中的局部变量
  • 静态变量
  • 交互到COM+中的托管物体
  • 物体含有finalizer

这也解释了,若忘记解绑static事件,则会发生内存泄漏。

.Net的GC采用了分代算法,这是由于对全部内存进行一次完整GC花费太过昂贵,且新创建的物体通常生命周期较短。使用方法: GC.Collect(int n);

同时,C# 也提供了using语法糖(在Finalize中实现Dispose),并且在上面提供的博客地址和MSDN中,也有相关的GC使用最佳实践等。

那么回到开篇的问题,CLR的GC是否能正确处理循环引用? 答案是。 这是因为CLR采取引用标记的算法,从root出发,由于循环引用的两个类并没有被标记(假设root中没有指向其中任一物体),则同时被清除。 若是采用引用计数法,则无法处理此类问题

弱引用

在.Net 4.0中,新增了弱引用类型,即当一个物体离开作用域或者被置为null(即可以被GC回收)时,若弱引用物体被引用,则可以重新取回该物体。 这样会有效避免创建物体所造成的性能损失。 使用方法也较为简单

WeakReference w = new WeakReference(MyHugeObject);

由于Unity目前并不支持.Net 4.0 , 若想要进一步了解详情,可查看这一篇文章

具体应用及小结

了解了相关GC的原理,可以在对项目中优化堆内存,减少内存泄漏(c# 同样会出现)等方面有所帮助。

关于显示调用GC.Collect(),目前看来是不太必要。 原来在Unity切换场景是手动调用,但是在进行UWA检测时发现,切换场景是GC发生了2次。 通过查看Profiler时发现,若使用异步加载场景API,则会自动调用一次GC,造成GC调用重复。

关于GC的优化 –如使用对象池等方法,以后会开出几篇专门研究。

~周末快乐~