[.NET] GC垃圾回收机制

前言:

在.NET程序开发中,为了将开发人员从繁琐的内存管理中解脱出来,将更多的精力花费在业务逻辑上,CLR提供了自动执行垃圾回收的机制来进行内存管理。开发人员甚至感觉不到这一过程的存在。CLR执行垃圾回收的过程,有以下几点:

  • 如何判断哪些对象是可以进行回收的,哪些是要保留的?
  • 对象在堆上是如何分布的?何时执行垃圾回收?
  • 垃圾回收的过程如何进行的?有哪些优化策略?

判断哪些对象需要进行回收

垃圾回收,第一步要判断哪些对象需要被GC回收掉。简而言之,那些在代码的任何位置也无法访问到的对象是需要被GC回收掉的。

GC借助于应用程序根(Application Roots)和对象图(Object Graph)来判断哪些对象需要被回收。应用程序根保存了堆上对象的引用,如果一个对象没有直接或者间接的被应用程序根所引用,那么就说明没有任何代码可以访问到它,因此这个对象可以被回收。

应用程序根只是一个入口点,一个对象可能持有其他一个或者多个对象的引用,这种对象间的引用关系构成了对象图。每次创建新对象,在复制引用、删除引用,或者执行垃圾回收之后,CLR都会自动更新它。

对象如何分配在堆上

.NET中的对象创建在托管堆上,托管堆维护着一个指针,这个指针标识了下一个将要创建的新对象所在位置,通常称作新对象指针。该指针总是位于托管堆的末尾。当使用new运算符创建对象时,CLR将会执行下面几个主要操作:

  • 计算此对象所要分配的内存大小;
  • 检查托管堆,确保托管堆有足够的空间来建立这个对象。如果空间够,将会调用构造函数来创建对象,并在托管堆末尾创建对象,然后返回对象在托管堆上的引用。引用的地址即为新对象指针所指向的地址。如果空间不够,则会执行一次垃圾回收来释放空间;
  • 修改新对象指针的值,使它指向上一个可用的地址。

垃圾回收的执行过程

GC有许多算法,常见的算法有Reference Counting, Mark Sweep, Copy Collection等,目前主流的虚拟系统.NET CLR, Java VM都采用的Mark Sweep算法。Mark-Sweep标记清除阶段:先假设Managed Heap中所有对象都可以被回收,然后找出不能回收的对象,给这些对象打上标记,最后Managed Heap中没有打标记的对象都可以被回收。Compact压缩阶段:对象回收之后Managed Heap内存空间变得不连续,在Managed Heap中移动这些对象,使他们重新从Managed Heap基地址开始连续排列。

执行垃圾回收的时机:

  • 当通过new关键字创建对象时,如果发现托管堆所占用的内存已经达到一个临界点,就会进行垃圾回收;
  • 应用程序退出时也会执行一次垃圾回收;
  • 调用System.GC.Collection()方法强制执行;

垃圾回收过程中的优化策略

垃圾回收过程中还采用了一些优化策略,主要时对象代(Object Generation)和大对象堆(Large Object Heap)

1. 对象代

对象共分了三个代级:

  • 第0代:新近分配在托管堆上的对象,从来没有被GC回收过,任何一个新对象,当它第一次被分配在托管堆上时,就是第0代;
  • 第1代:经历过一次垃圾回收后,依然保留在堆上的对象;
  • 第2代:经历过两次或者以上垃圾回收后,依然保留在堆上的对象。

当进行垃圾回收时,垃圾回收器将会首先检查所有的第0代对象,并对其中可回收的对象进行清理。如果清理后获得足够的空间,经历过垃圾回收后的对象将提升为第1代对象。

如果所有的第0代对象都检查过了,但是内存空间还不够,那么将会检查第1代对象的可访问性,并进行垃圾回收。此时,如果经历过垃圾回收的第1代对象仍保留在堆上,则会升级为第2代对象。类似的,如果内存仍不够用,将会对第2代对象进行检查和垃圾回收。如果第2代的部分对象在此次垃圾回收后仍然保留在堆栈上,它依然是第2代对象。因为总共定义了3代对象。如果第2代对象在进行完垃圾回收后空间依然不够用,则会抛出OutOfMemoryException异常。

可见,最容易清理掉的就是那些新对象。

2. 大对象堆

垃圾回收的过程中有一个很影响性能的地方,就是在压缩的过程中,因为要批量地挪动对象,以填充腾出来的空间,如果对象很大,那么要移动的数据量就会很大。

如果将大对象直接分配在第0代,那么第0代的空间很快就会被占满,从而迫使CLR执行一次垃圾回收,这样执行垃圾回收的次数就会变得很频繁。

因此,第二个优化策略就是采用大对象堆,当对象大小超过85kb时,就会被分配在大对象堆上。大对象堆有几个特点:

  • 没有代级的概念,所有对象都被视为第2代对象;
  • 不进行对象移动和空间压缩,因为移动大对象是相对耗时的操作。
  • 对象不会被分配在末尾,而会在链表中找合适的位置,因此会存在碎片问题。

至此,就对.NET GC有了一个基本的了解。感谢您的阅读~

posted @ 2017-09-14 15:46  Yang-Fei  阅读(699)  评论(0编辑  收藏  举报