垃圾回收GC:.Net自己主动内存管理 上(二)内存算法
前言
.Net下的GC全然攻克了开发人员跟踪内存使用以及控制释放内存的窘态。然而,你或午想要理解GC是怎么工作的。此系列文章中将会解释内存资源是怎么被合理分配及管理的,并包括很具体的内在算法描写叙述。同一时候。还将讨论GC的内存清理流程及什么时清理,怎么样强制清理。
内存算法
(假设堆中没有可用内存空间时,new操作符将会抛出OutOfMemoryException异常)GC是如何知道一个对象是否还被程序使用呢?你能够想象一下。这不是一个easy回答的问题。
另外,线程栈中不论什么局部变量或參数对象指针也被看作程序根节点(roots)的一部分。
最后。全部包括托管堆对象指针的CPU寄存器也被看作程序根的一部分。这组激活的根由JIT编译器和CLR维护,并可被GC的算法系统訪问。
其他节点假设遇到同样情况也会停止,直到全部对象映射完毕。这种映射方式有两个目的。第一。避免反复映射一个或一组对象大大提升了程序性能。第二。它避免了映射死循环。
如今GC能够直线的訪问堆,查找垃圾对象占用的连续性的内存块。然后GC把非垃圾对象占用的内存空间移动到垃圾对象所占用的内存块上(标准的memcpy操作)。并删除全部堆中内存间隙(对象所占用内存块之间的间隙)。当然,这个内存块的移动操作会影响到全部关联的内存指针,由于内存地址发生了变化。因此GC必须改动程序根节点(roots)并确保全部受影响的指针指向对象的新地址。另外。假设不论什么对象包括一个指向其他对象的指针。GC同一时候也会负责纠正这些指针。大家还能够參考我的还有一篇文章:
深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能
。下图是运行垃圾回收之后的托管堆:
在全部垃圾对象被回收后。全部非垃圾对象又一次变得紧凑,非垃圾对象指针也会全部被修复。NextObjPtr会被放到最后一个非垃圾对象之后。这时,new操作符又開始偿试创建新对象,程序请求的资源也被成功创建。
你能够看到,GC产生了一次明显的性能消耗,这是使用GC的主要缺点。然而,记住GC仅在堆被占满的情况进行回收,在回收之前。托管堆明显比C语言执行时的堆快非常多。GC还提供了一些优化操作能够大大提高垃圾回收的性能。兴许文章会讨论GC怎么优化垃圾回收的。
有一些非常重要的点要指出来。你不再须要通过写代码去管理程序资源的寿命。文中一開始提到的两种BUG也将不再存在。首先。它不可能再产生资源泄漏,由于不论什么程序根节点(roots)訪问不到的资源(即垃圾)都会被回收。其次,你也不再可能訪问一个被释放掉的资源,由于假设资源可以被訪问到,它就永远不会被释放。假设资源不可以被訪问到。我们也没有理由去訪问它。
以下代码展示了资源是怎么样被分配与管理的:
class Application { public static int Main(String[] args) { //在堆中创建ArrayList对象,myArray如今作为程序根节点 ArrayList myArray = new ArrayList(); // 在堆中创建10000个对象 for (int x = 0; x < 10000; x++) { myArray.Add(new Object()); } // 如今,myArray是一个根(在线程栈里)。 // 因此。myArray和10000个对象都是可达的 Console.WriteLine(myArray.Length); // 在代码中myArray最后一个引用(Console.WriteLine(myArray.Length))之后, myArray 不再是一个根 // 不必非要等到此方法返回后,JIT编译器会知道在myArray最后一个引用之后把它标识为非根节点 // 由于myArray不再是根节点,全部10001个对象不再可达 // 它们会被看作是垃圾 // 可是它们会一直存在直到GC对它们进行回收 } }
假设GC这么出色,为什么C++不使用它呢?原因是GC必须可以识别程序根节点(roots),而且必须可以找到全部对象指针。C++里同意指针进行类型转换,所以不可能确定一个指针指向的是什么。
在一般语言执行时CLR中,托管堆总是可以确定对象的确切类型,并可通过元数据metadata信息决定一个对象的成员指向哪些其他对象。
总结
了解内存算法让你知道GC为什么快。GC的什么操作会很消耗性能,从而让你对自己的程序的性能消耗有一个清晰的概念。
下一文中我将继续介绍垃圾回收GC的自己主动内存管理:终节器Finalization。