• 软件测试技术
  • 软件测试博客
  • 软件测试视频
  • 开源软件测试技术
  • 软件测试论坛
  • 软件测试沙龙
  • 软件测试资料下载
  • 软件测试杂志
  • 软件测试人才招聘
    暂时没有公告

字号: | 推荐给好友 上一篇 | 下一篇

.NET中的垃圾回收

发布: 2007-6-14 17:33 | 作者: 网络转载 | 来源: 网络 | 查看: 22次 | 进入软件测试论坛讨论

领测软件测试网

MILY: 黑体; mso-ascii-font-family: Arial">目录

l         导言

l         关于垃圾回收

l         垃圾回收算法

m        应用程序根(Application Roots

l         实现

m        阶段I: 标记(Mark

m        阶段II: 整理(Compact

l         终结(Finalization

l         垃圾回收性能的优化

m        弱引用(WeakReference

m        代(Generations

l         垃圾回收相关的神话

 

导言

       微软声称.NET是一种革命性的编程技术。许多要素使它成为大多数开发人员的首选。本文我们将要讨论一下.NET Framework中一个很主要的优势——内存和资源管理的便捷性。

 

关于垃圾回收器

       每个程序都会使用一定次序的资源,或内存缓冲区,或网络连接,或数据库资源等等。实际上,在面向对象环境中,每种类型都被看作是程序的某些有效资源。为了使用这些资源,必须分配部分内存来描述这种类型。

 

       资源访问按一下步骤进行:

 

1.    为类型分配内存来描述资源。

2.    初始化资源,把资源设置成初始化状态,使资源可用。

3.    通过访问类型实例的成员来使用资源(按需求重复)。

4.    销毁资源状态以清除资源。

5.    释放内存。

 

.NET中的垃圾回收器(GC)完全彻底帮助开发人员从追踪内存的使用和确定什么时候释放内中解脱出来。

 

Microsoft® .NET CLR (公共语言运行库)要求所有资源都从托管堆中进行分配。你无需释放托管堆中的对象——当应用程序不再需要这些对象时,对象将被自动释放。

 

内存不是无限的。垃圾回收器需要执行回收以便释放内存。垃圾回收器的优化引擎会对已做的分配选择最好的回收时间(准确标准由微软提供)。当垃圾回收器执行回收时,它先找出托管堆中不再被应用程序使用的对象然后执行相应操作收回这些对象的内存空间。

 

       然而,为了进行自动内存管理,GC必须知道根的位子。也就是说,它应该知道一个对象什么时候不再被应用程序使用了。在.NET中,GC是通过一个称之为元数据的东西了解到这些的。.NET中使用的每种数据类型都通过元数据来描述它。在元数据的帮助下,CLR知道内存中每个对象的布局,在垃圾回收的整理阶段给GC提供帮助。没有这些信息,GC将不会知道一个对象在哪儿结束和下一个从哪儿开始。

 

垃圾回收算法

应用程序根(Application Roots

       每个应用程序都有一套根(Roots)。根标识存储位置,这个位置或者指向一个托管堆的对象,或者指向一个空对象(null)。

 

比如:

 

l         一个应用程序中所有全局和静态对象指针。

l         一个线程堆栈中所有局部变量/参数对象指针。

l         托管堆中所有CPU登记的对象指针。

l         FReachable队列中的对象指针。

 

活动根的表由JIT编译器和CLR维护,并且对于垃圾回收器的算法是访问。

 

实现

.NET中的垃圾回收是用跟踪回收实现的,确切的说CLR实现了标记(Mark)/整理(Copact)回收器。

 

这个方法有以下两个阶段组成:

 

阶段I:标记(Mark

找到可以被收回的内存。

 

GC开始运行时,它假设堆中的所有对象都是垃圾。换句话说,它假设应用程序的根没有指向堆中的的任何对象。

 

阶段I中包含下列步骤:

 

1.  GC识别存活对象的引用或应用程序根。

2.  从根开始遍历,建一张可以从根遍历的所有对象的图。

3.  如果GC准备尝试添加一个已经在图中的对象,它就停止这条路径的遍历。这样做有两个目的,第一个是极大的优化性能,因为它不会遍历一套对象一次以上。第二是防止当有对象的循环连接列表时而发生死循环,因此循环被有效的控制了。

 

一旦所有的根都被检查完后,垃圾回收器的图中包含了所有可以从应用程序根遍历到的对象。任何不再图中的对象都不能被应用程序访问到,也就是所谓的垃圾。

 

阶段II:整理(Compact)

把所有存活的对象移到堆的末端,空出堆顶端的空间。

 

阶段II包含下列步骤:

 

1.  现在GC线性地遍历堆,寻找邻近的垃圾对象块(现在被认为是空闲空间)。

2.  然后GC往下移动内存中的非垃圾对象,去掉堆中的所有空隙。

3.  移动内存中的对象导致对象指针失效。因此GC需要修改应用程序的根使对象的指针指向新的位置。

4.  另外,如果对象包含一个指向其它对象的指针,GC也会负责纠正这些指针。

 

在所有垃圾被标识完以后,所有的非垃圾对象也被整理,并且所有非垃圾对象的指针也被修正,最后一个非垃圾对象后的指针指向下一个被添加对象的位置。

 

终结(Finalization)

 

.NET Framework的垃圾回收器能暗中追踪由应用程序创建的对象的生命周期,但是当它遇到对象包装了非托管资源(比如文件、窗口或网络连接等)时却无能为力。

 

一旦应用程序不再使用那些非托管资源时需要显示地释放它们。.NET Framework为对象提供了终结(Finalize)方法:在垃圾回收器收回这个对象的内存时,必须执行对象的这个方法来清除它的非托管资源。由于缺省的Finalize方法什么都没做,如果需要显示清除资源必须覆盖这个方法。

 

如果一把Finalize方法当作只是C++中析构函数另外一个名字那也不足为怪。虽然它们都被赋予了释放对象占有的资源的任务,但是它们还是有很不相同的语义。在C++中,当对象推出作用域时析构函数会立刻被调用,而Finalize方法是在起动垃圾回收清除对象时才被调用的。

 

.NET中,由于终结器(Finalizer)的存在使得垃圾回收的工作变得更加复杂了,因为它在释放对象前增加了许多额外的操作。

 

无论什么时候,在堆上分配一个含有Finalize方法的新对象时,都会有一个指向这个对象的指针被添加到一个称为Finalization队列的内部数据结构当中。当对象不能再次被遍历到时,GC就认为这个对象是垃圾。GC首先扫描Finalization队列查找这些对象的指针,当指针被找到时,把它从Finalization队列中去掉并添加到另外一个名为FReachable队列的内部数据结构中,使这个对象不再是垃圾的一部分。这时,GC完成了确定垃圾。然后整理(Compact)可收回的内存,由专门的线程负责清空FReachable队列并执行对象的Finalize方法。

 

第二次垃圾回收器被触发的时候,它把被终结(Finalize)的对象看作真正的垃圾,然后简单的释放它们的内存。

 

由此可知当一个对象需要终结时,它先死,然后存活(复活),然后再次并且最终地死去。推荐避免使用Finalize方法,除非有需要。Finalize方法会增加内存压力,因为直到两次垃圾回收被启动时,对象占用的内存和资源才会得到释放。因此你无法控制两次Finalize方法执行的顺序,它可能会导致无法预料的后果。

垃圾回收性能的优化

l         弱引用(WeakReference

l         代(Generations

 

弱引用(WeakReference

       弱引用(WeakReference)是提高性能的一种方式,用于减少托管堆中大对象的压力。

      

       当一个根指向一个对象时,它被称为这个对象的一个强引用并且这个对象不能被回收,因为应用程序能遍历到这个对象。

 

       当一个对象有一个指向它的弱引用(WeakReference)时,基本上是指如果有内存请求并且GC启动时,这个对象可以被回收,当应用程序再次尝试去访问这个对象时,访问将会失败。另一方面,为了能访问一个被弱引用(WeakReference)的对象,应用程序必须获得一个对这个对象的强引用。如果应用程序在垃圾回收器回收这个对象之前获得了它的强引用,GC将不能回收这个对象,因为有这个对象的强引用存在。

 

       托管堆包含两个管理弱引用(WeakReference)的内部数据结构:短弱引用表和长弱引用表。

 

       两种类型的弱引用:

 

l         短弱引用不追踪复苏。

也就是说,一个有短弱引用的对象会被立即收回,而不用等到运行Finalize方法。

l         长弱引用追踪复苏。

也就是说,只有当长弱引用表中的对象的存储空间可收回的时候GC才回收这个对象。如果对象有Finalize方法,是在Finalize方法被调用了之后并且对象不能复活了。

 

       这两个表简单的存放着分配在托管堆中对象的指针。最初,两个表均为空。当你创建一个弱引用(WeakReference)对象时,对象不从托管堆中分配。而是在一个弱引用表中分配一个空的存储位置;短弱引用使用短弱引用表,长弱引用使用长弱引用表。

 

       让我们看一个例子,看看GC运行时会发生些什么。下面的图(图1和图2)显示了GC运行前和运行后所有内部数据结构的状态。

 

1GC运行前

 

2GC运行后

 

以下是GC运行时进行的操作:

 

1.  GC为所有可遍历的对象建一张图。在上面的例子中,图包含对象B,C,E,G

2.  GC扫描短弱引用表。如果表中指针指向的对象不在图中,那么这个指针标识的是一个不可遍历的对象,短弱引用表中的这个位置置为null。在上面的例子中,对象D的位置置为null,因为它不是图的一部分。

3.  GC扫描Finalization队列。如果队列中的指针所指的对象不在图中,那么这个指针标识一个不可遍历的对象,指针从Finalization队列中移到FReachable队列中。这时,对象被认为是可遍历的,所以加到图中。在上面的例子中,对象A,D,F是不包含在图中但看作是可遍历的对象,因为它们属于Finalization队列。进而Finalization队列被清空。

4.  GC扫描长弱引用表。如果表中的指针指的对象不在图中(现在图包括FReachable队列中指针所指的对象),那么指针标识一个不可遍历的对象,所在位置置为null。由于对象CF都包含在图中,都不置null

5.  GC整理(Compact)内存,挤出不可遍历对象留下的空隙。在上面的例子中,对象H是唯一从堆中删除的对象,它所分配的内存被收回。

 

代(Generations

       由于垃圾回收要在停止整个程序的情况下才能完成,它们可能会在程序执行期间进行任意长时间的中断。GC也有可能中断为满足实时系统的需求而要求及时响应的事件。

 

       GC中有一个特征叫代(Generations),就是专门为提高性能而设计的。一个多代的GC是通过对观察用各种语言编写的大部分程序而得到两个事实进行仔细分析而得到的:

 

延伸阅读

文章来源于领测软件测试网 https://www.ltesting.net/

TAG: net 中的 垃圾 回收


关于领测软件测试网 | 领测软件测试网合作伙伴 | 广告服务 | 投稿指南 | 联系我们 | 网站地图 | 友情链接
版权所有(C) 2003-2010 TestAge(领测软件测试网)|领测国际科技(北京)有限公司|软件测试工程师培训网 All Rights Reserved
北京市海淀区中关村南大街9号北京理工科技大厦1402室 京ICP备10010545号-5
技术支持和业务联系:info@testage.com.cn 电话:010-51297073

软件测试 | 领测国际ISTQBISTQB官网TMMiTMMi认证国际软件测试工程师认证领测软件测试网