您的位置:首页 > 其它

学习笔记(3)——.net中的垃圾收集(翻译)

2004-06-01 14:33 375 查看
Garbage Collection in .NET
By Amit Kukreja
http://www.codeproject.com/dotnet/garbagecollection.asp#xx821174xx
.net中的垃圾收集
这篇文章讲了在.net中,垃圾收集是如何执行的!
这个很关键,虽然很多人认为它太理论化,就像大学中的编译原理教科书(Koffer注)

内容目录:

简介
关于垃圾收集
垃圾收集算法
应用程序根
执行,应用
第一阶段:标记
第二阶段:收集,紧锁内存
释放资源
垃圾收集执行优化
弱引用

关于垃圾收集的一些观点

简介
.net是微软带给程序社区的大肆渲染的革命性的技术。许多因素促使大部分开发人员使用了.net。在这篇文章中,我们将探讨.net FrameWork的一个基本的优势——内存和资源管理的灵活性

关于垃圾收集
每个程序都会使用这样那样的资源——内存空间、网络连接、数据库资源等等。事实上,在面向对象的环境中,每一种类型代表了一种程序可用的资源。为了使用这些资源,必须分配一定的内存来指向它。

访问一个资源的必要步骤如下:
1、为某类资源分配内存,从而代表这个资源。
2、初始化内存,给资源赋予初值使它成为可用的资源。
3、使用该类型的实例成员来访问这个资源。
4、清空资源状态,释放系统资源
5、释放内存空间
(每次遇到这种类型的翻译,都会想起外国程序员的敬业精神,i.e.如果让你说说这个过程,你的答案会怎样?试试看!——Koffer注)

.net垃圾收集器彻底使程序开发人员不必跟踪内存的使用状况,不必知道何时该释放内存。

微软的.net CLR要求所有的资源分配在托管的堆中。你根本不需要从释放托管堆的对象,因为他们会在应用程序不需要的时候自动释放!

内存并非是无限的。为了释放一定的内存,垃圾收集器必须执行收集操作。垃圾收集器在不断的优化引擎来决定最佳的垃圾收集执行点(确切的临界值被微软封闭),这个基于已经分配的内存。当垃圾收集器执行收集的时候,在托管堆中检查不被应用程序使用的对象,然后执行必要的操作来收回内存。

然而对于自动的内存管理来讲,垃圾收集器必须知道根的位置,也就是说,它应该知道什么时候一个对象在程序中将不被使用了。在.net中,GC之所以知道这个,都应该归功于元数据(metadata).在.net软件中的每一种数据类型都包含了元数据来描述它,有了元数据,CLR就知道了每个对象在内存中的精确位置,这个可以帮助垃圾收集器在压缩收集内存阶段的忙。如果垃圾收集器不了解这些,将不可能知道一个对象实例结束和另一个对象的开始。

垃圾收集算法

应用程序根
每一个应用程序都有一组根节点。根决定了存储的位置,这个位置指向托管堆中的对象,或者如果设置为空的话,就指向空节点。

例如:
应用程序中所有的全局和静态的对象指针。
任何指向线程堆栈的局部变量或参数。
任何托管堆中的包含指针对象的寄存器。
Pointers to the objects from Freachable queue
活动根列表被JIT编译器和CLR所维护,而且可以被垃圾收集器算法访问到。

执行
在.net中的垃圾收集算法是通过跟踪和执行CLR的标记/整理算法实现的。
这个方法包含如下的两个阶段:
第一阶段:标记
寻找那些可以被释放的内存。
当垃圾收集器开始运行以后,就假定对中所有的对象都是垃圾了。也就是说,假设如下前提:在这个堆中,没有一个应用程序的根指向其他的任何一个对象。

下述的步骤包含在第一个阶段中:
1、GC确认活动对象的参考和应用程序根。
2、遍历所有的根从而构造一张可以从根到达其他对象的图。
3、如果GC试图增加一个已经存在于该图的对象,那么此时就停止从这个路径往下遍历。这样有两个目的:一方面、显著的提高了效率,因为并没有多次去遍历一组对象。另外,避免了无限的循环,你肯定会出现一个环状的连接对象表。那么通过这个就很好的处理了环的问题。

一旦所有的根节点都被检查过,那么垃圾收集器图就包含了所有从应用程序根节点可以通过某种途径访问的对象;那样的话,任何不在图中的对象就是在应用程序中不被访问的对象节点了,也就是说他们就被认为是垃圾了。

第二阶段:整理
把所有活动的对象移动到堆的底部,在堆的上部留出空闲。
第二阶段包含如下的步骤:
1、现在垃圾收集器线形的遍历堆,寻找连续的垃圾对象块(这里就是空闲的区域)
2、然后垃圾收集器在内存中把非垃圾对象向下移动,去掉堆中所有的缝隙。
3、在内存中移动对象会使指向对象的指针失效。所以垃圾收集器修改应用程序根,从而使指针指向新的位置。
4、另外,如果任何对象包含一个指向其他对象的指针,垃圾收集器将会纠正这些指针。

所有的垃圾被确认以后,非垃圾就被紧缩到了一起,而且非垃圾的指针也被修改过了,只有当一个非垃圾对象确认了自己的位置以后,紧接着的对象才能往上边增加。

释放资源

.net Framework的垃圾收集器暗中跟踪了应用程序创建的对象的生命周期,但是如果遇到非托管的资源(例如,文件、窗口或者网络连接)时候将会出现失败。

这些非托管资源在程序执行完毕后必须被显式的释放,.net Framework提供了Object.Finalize方法:垃圾收集器必须运行在对象被收回之前就要去执行,对象上来清空自己的非托管资源。因为Finalize方法缺省情况下什么也不做,如果你需要清除的话,必须重写该方法。

如果你认为Finalize只是在C++中的destructors的另外一个名字也不是很奇怪的事情。尽管,他们都被赋予了释放对象资源的责任,但是他们有区被非常大的语义。在C++中,一旦对象超出了某个局域,desturctor就马上执行。但是,Finalize方法只有当垃圾收集器方法开始清除一个对象的时候才执行。

在.net中,finalizer的潜在存在,在释放一个对象之前,增加了额外的步骤使垃圾工作变得复杂了。

无论什么时候,一个具有Finalize方法的新对象被分配到堆上的时候,就有一个指向该对象的指针被放到了一个内部的叫做“终止队列(Finalization queue)”的数据结构。当一个对象不能被访问到的时候,垃圾收集器就认为这个对象是垃圾。垃圾收集器扫描释放队列来寻找这些对象的指针,每找到一个指针,这个指针就从释放对列被删除,把它转移到另外一个内部的叫做“Freachable queue”数据结构中,使这个对象再也不是垃圾了。在此,垃圾收集器已经完成了确认垃圾的工作,然后通过执行每个对象的Finalize方法整理这些可以被回收的内存,它会启动一个特殊的实时线程清空“Freachable queue”。

下次,垃圾收集器被调用的时候,就会发现这些对象是真正的垃圾,他们占用的内存就被简单的释放了。

那么就是说,当一个ie对象需要释放的时候,他首先死去,然后活过来(复兴),最后再死去。建议避免使用Finalize方法,除非必须使用。Finalize方法会增加内存的压力,因为它不让一个将要被释放的对象释放内存和资源,直到两次执行垃圾收集。因为你无法控制Finalize方法的执行顺序,所以他将导致不可以预期的结果。

垃圾收集行为优化
弱引用


后边的内容不翻译了,因为在CSDN上找了一个类似的文章,参考就好
http://www.csdn.net/develop/read_article.asp?id=19987
http://www.csdn.net/develop/read_article.asp?id=19679
http://www.csdn.net/develop/read_article.asp?id=19842

感谢作者和上边的这个朋友!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: