垃圾回收算法之引用跟踪算法

《垃圾回收算法之引用计数算法》这篇博客里,我们说到了引用计数算法的缺陷:会造成循环引用的问题。本篇的引用跟踪算法则客服了这种缺陷。

在引用计数算法中,对于每个引用的对象,我们有一个额外的字段专门计数这个对象被引用的次数,当次数为0时,则视为垃圾。那在引用跟踪算法中,我们如何判断这个对象是垃圾呢?答案是:对象可达图。

一.对象可达图

它的含义是,首先将所有的对象标记为垃圾并将它们视为根,从根出发,将所有的正在使用的对象串联起来,直至完毕,不被串联的对象即为“不可达”,即被视为垃圾,”可达” 的对象则被认为是有用的,在垃圾回收的过程中幸存下来。

static void Main(string[] args)
{
   var obj1 = new object();//step1
   DoSomething();//step2
   var obj3 = obj1;//step3
}//step4

static void DoSomething()
{
   var obj2 = new object();
}

我们来看下面一段代码,根据可达图的含义来画出对象可达图。从总体来看彼此的调用关系如下:

我们假设,在运行到step3之后,运行step4之前,进行了一次垃圾回收。

首先:找出所有的根对象,标志其为”垃圾”,这个是通过将同步块索引的一位设为0来完成的,我们称这个阶段为”标记”阶段。

在运行到step3后,obj1和obj2和obj3就是这里的3个根,其中根obj2在堆栈中已经被pop出去了,因此堆栈中只有两个根对象obj1和obj3,如图所示:

因为根obj1均引用了object对象1,所以object对象1的同步块索引的一位被置为1,以标记其不是垃圾,然后检查根obj3的对象引用情况,发现它也引用了object对象1,当它刚要标志其同步索引块的一位时,发现object对象1已经被标记了,则不重新标记它。这样就避免了因循环引用而产生死循环。

需要注意的是,在标记object对象1时,发现它引用了其他的对象(假设为a),那么对象a也对被标记。

标记过程会持续,依次检查完所有的根。标记完毕后,堆中的对象要么被标记要么未标记,就会形成一个对象可达图,图中可达的对象不是垃圾,不可达的对象被视为垃圾。知道了哪些是垃圾后,下一步就进入了压缩阶段。

二.压缩阶段

压缩阶段主要的任务是将幸存的对象压缩成连续的,因为根据应用程序的局部性原理,这样更有利于减少程序集,释放了内存,解决了堆的空间碎片化问题,同时提高了缓存命中率。对于下图而言,我们就是要把acef对象压缩成连续的,其实很简单,我们只需要把c对象挪到a后面,e对象挪到c后面,依此类推,即可使这些内存对象变成连续的。

在完成这一步后,其实两个问题亟待解决,第一个是c内存压缩后,其在堆中的实际位置变化了,根引用c的位置还是原来的,如果听任下去,将会访问旧的内存地址而造成内存损坏。所以,还要将根中引用c的地址减去在压缩中偏移的字节数,这样就能保证每个根引用的还是原来的c,只不过对象在内存中变换了位置。

第二个亟待解决的问题是,指向下一个对象在堆中的分配位置NewObjPtr指针也要进行偏移字节数的计算,这样才能保证新对象分配的内存与原有的堆内存是连续的。

下面是垃圾回收后的托管堆:

经过垃圾回收过后,内存不可能泄漏了,因为对象可达图中的不可达对象,都会被回收;其次不可能因为访问不可达对象而造成内存损坏,因为现在只能引用可达的对象,不可达的对象是引用不了的。

参考文档

《CLR via C#》(第4版)

时间: 2024-10-18 05:25:13

垃圾回收算法之引用跟踪算法的相关文章

JVM垃圾回收(四)- GC算法:实现(1)

GC算法:实现 上面我们介绍了GC算法中的核心概念,接下来我们看一下JVM里的具体实现.首先必须了解的一个重要的事实是:对于大部分的JVM来说,两种不同的GC算法是必须的,一个是清理Young Generation的算法,另一种是清理Old Generation的算法. 在JVM里有各种各样的这种内置算法,如果你没有特别指定GC算法,则会使用一个默认的.适应当前平台(platform-specific)的算法.接下来我们会解释每种算法的工作原理. 下面的列表提供了一个快速的预览,关于哪些算法可能

.NET 托管堆和垃圾回收

托管堆基础 简述:每个程序都要使用这样或那样的资源,包括文件.内存缓冲区.屏幕空间.网络连接.....事实上,在面向对象的环境中,每个类型都代表可供程序使用的一种资源.要使用这些资源,必须为代表资源的类型分配内存.以下是访问一个资源所需步骤:1.调用IL指令newobj,为代表资源的类型分配内存.(C# new操作符)2.初始化内存,设置资源的初始状态.(一般指构造函数)3.访问类型的成员来使用资源.(使用成员变量.方法.属性等)4.摧毁资源的状态以进行清除.(???Dispose???)5.释

重温CLR(十五) 托管堆和垃圾回收

本章要讨论托管应用程序如何构造新对象,托管堆如何控制这些对象的生存期,以及如何回收这些对象的内存.简单地说,本章要解释clr中的垃圾回收期是如何工作的,还要解释相关的性能问题.另外,本章讨论了如何设计应用程序来最有效地使用内存. 托管堆基础 每个程序都要使用这样或那样的资源,包括文件.内存缓冲区.屏幕空间.网络连接.数据库资源等.事实上,在面向对象的环境中,每个类型都代表可提供程序使用的一种资源.要使用这些资源,必须为代表资源的类型分配内存.以下是访问一个资源所需的步骤 1 调用IL指令newo

JVM优化 垃圾回收 算法 垃圾收集器 GC日志可视化查看

今日内容了解什么是垃圾回收掌握垃圾会回收的常见算法学习串行.并行.并发.G1垃圾收集器学习GC日志的可视化查看 1.什么是垃圾回收?程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存 资源,最终将导致内存溢出,所以对内存资源的管理是非常重要了. 1.1.C/C++语言的垃圾回收在C/C++语言中,没有自动垃圾回收机制,是通过new关键字申请内存资源,通过delete 关键字释放内存资源.如果,程序员在某些位置没有写delete进行释放,那么申请的对象将一直占用内存资源,

常用的垃圾回收算法

引用计数法 对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1. 只要A对象的引用计数器的值为0,则对象A就不可能再被使用. 实现也很简单,只需要为每个对象配备一个整型的计数器即可. 缺点: 1.无法处理循环引用的情况 2.引用计算器要求在每次引用产生和消除的时候,需要伴随一个加法操作和减法操作,对系统性能有一定影响. A引用了B,B又引用了A,因此A和B的引用计数器都不为0,但是系统中却 不存在任何第3个对象引用了A和B. 这种情况下,A和B是无

JVM 垃圾回收算法

在说垃圾回收算法之前,先谈谈JVM怎样确定哪些对象是"垃圾". 1.引用计数器算法: 引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,计数器-1,当计数器为0的时候,JVM就认为对象不再被使用,是"垃圾"了. 引用计数器实现简单,效率高:但是不能解决循环引用问问题(A对象引用B对象,B对象又引用A对象,但是A,B对象已不被任何其他对象引用),同时每次计数器的增加和减少都带来了很多额外的开销,所以在JDK1.1之后,

垃圾回收算法手册:自动内存管理的艺术 BOOK

垃圾回收算法手册:自动内存管理的艺术 2016-03-18 华章计算机 内容简介 PROSPECTUS 本书是自动内存管理领域的里程碑作品,汇集了这个领域里经过50多年的研究沉积下来的最佳实践,包含当代最重要的垃圾回收策略与技术,著译双馨. 几乎所有的现代编程语言都采用了垃圾回收机制,因此深入了解此方面内容对于所有开发者而言都大有裨益.对于不同垃圾回收器的工作方式,以及当前垃圾回收器所面临的各种问题,这本权威手册都提供了专业的解答.掌握这方面的知识之后,在面对多种不同的垃圾回收器以及各种调节选项

Java内存回收(2)——垃圾回收算法

如果还没看过第一篇的朋友请移步:JAVA内存回收(1)-深入浅出Java垃圾回收机制 任何垃圾收集算法必须完成两件事情.首先,它必须检测出垃圾对象.其次,它必须回收垃圾对象所占用的堆空间并使之对程序重新可用. 垃圾检测通常通过定义一个根引用集并计算其可达对象集的方式来实现.一个对象,如果可以通过某条始于根引用的引用路径而被执行程序访问到的话,则称其为可达的(reachable).对程序而言,根引用始终是可以访问的.一个对象如果是可达的,则称其为活动对象:否则就被称为垃圾,因为它对程序的未来执行不

垃圾回收的概念与算法

GC中的垃圾,是指的是在内存中不在不再被使用的对象. 常见的垃圾回收算法 1.引用计数算法(无法回收循环引用的对象) 2.标记清除算法分为标记阶段和清除阶段(会产生内存的空间碎片) 3.复制算法(缺点是将系统内存折半,高效性是建立在存活对象少,垃圾对象多的前提下的) 在java新生代串行垃圾回收器中,使用了复制算法的思想,新生代分为eden,from,to三个部分.from,to空间成为survivor空间,用于存放未被回收的对象. 其中:新生代指得是存放年轻对象的空间. 老年代指的是存放垃圾回