垃圾回收

垃圾:广义上不需要再被引用的数据;狭义上不能被引用(不可达)的数据。

垃圾回收:自动回收不可达数据的机制,接触了程序员的负担。

垃圾回收器的设计目标

基本要求:语言必须是类型安全的,保证回收器能够知道数据元素是否为一个指向某内存块的指针。(静态类型ML、动态类型Java),类型不安全的语言(C/C++)。

性能目标:总体运行时间不显著增加应用程序的总运行时间;空间使用最大限度地利用可用内存;停顿时间当垃圾回收机制启动时,可能引起应用程序的停顿,这个停顿应该比较短(很少在实时应用中使用);程序局部性改善空间局部性和时间局部性。

可达性

可达性就是指一个存储块可以被程序访问到。根集不需要指针解引用就可以直接访问的数据。

根集的成员都是可达的;对于任意一个对象,如果指向它的一个指针被保存在可达对象的某字段或数组元素中,那么这个对象也是可达的

性质:一旦一个对象变得不可达,它就不会再变成可达的。

改变可达对象集合的操作

  • 对象分配:返回一个指向新存储块的引用

  • 参数传递和返回值

  • 引用赋值u=v ,v的引用被复制到u中,u中原有引用丢失;(可能)使u原来指向的对象变得不可达,并递归使更多对象变得不可达。

  • 过程返回:活动记录出栈,局部变量消失;根集变小,使一些对象变得不可达。

垃圾回收方法

  • 关注不可达:跟踪相关操作,捕获对象变得不可达的时刻,回收对象占用的空间

  • 关注可达:在需要时,标记出所有可达对象,回收其他对象

基于引用计数的垃圾回收器

每个对象有一个用于存放引用计数的字段,并按如下方式维护:

  • 对象分配:引用计数设为1

  • 参数传递:引用计数+1

  • 引用赋值:u=v ,u指向的对象引用减1,v指向的对象引用加1

  • 过程返回:局部变量指向对象的引用计数减1

如果一个对象的引用计数为0,在删除对象之前,此对象中各个指针所指对象的引用计数减1

特点:开销较大,但不会引起停顿

循环垃圾

三个对象相互引用,没有来自外部的指针,又不是根集成员,都是垃圾,但是引用计数都大于0。

基于跟踪的垃圾回收

标记-清扫式垃圾回收

一种直接的、全面停顿的算法,分成两个阶段:

  • 标记:从根集开始,跟踪并标记出所有的可达对象

  • 清扫:遍历整个堆区释放不可达对象

如果把数据对象看作节点,引用看作有向边,那么标记的过程实际上是从根集开始的图遍历过程

标记-复制式垃圾回收

目标:只处理可达对象(减少回收时间)将可达对象安排到一起(减少内存碎片)

堆空间被分为两个半区,应用程序在某个半区内分配存储,当充满这个半区时,开始垃圾回收。回收时,复制可达对象到另一个半区(需修改引用地址)。回收完成后,两个半区角色对调。

缺点是一般预留区域的内存无法使用。

标记-整理式垃圾回收

对可达对象进行重定位可以消除存储碎片:

  • 与标记-复制法将对象移动到预留另一半区不同,标记-整理法把可达对象移动到堆区的一端,另一端则是空闲空间

  • 空闲空间合并成单一块,提高分配内存时的效率

垃圾回收算法比较

  • 标记- 清扫式垃圾回收:回收效率不高,容易造成碎片

  • 标记-复制式垃圾回收:只需遍历可达对象,管理区域大部分对象为垃圾对象时效率高只需移动少量可达对象),反之则效率低

  • 标记-整理式垃圾回收:将可达对象移动到堆区的一端,需遍历整个区域;管理区域内大部分对象为可达对象时效率高经过之前的整理,大部分对象已经到位),反之则效率低。

分代式垃圾回收

对象生存周期特征:大多数对象刚创建不久后就不再使用(短命);存在时间越长的对象,通常被回收的几率越小(命硬)

根据生存周期,对对象分代管理。新创建的对象均放入年轻代区域(从幼年期开始),年轻代空间不足时,对该区域进行回收(标记-复制,因为垃圾比较多);熬过回收的对象移入下一区域(进化);老年代空间不足时,对该区域进行回收(标记-整理,因为垃圾比较少)

Last updated