垃圾回收机制是如何工作的?

29

以通俗易懂的方式解释,在垃圾回收机制中,如何工作?

一个对象如何被识别为可供垃圾回收?

此外,在垃圾回收算法中,引用计数(Reference Counting)、标记-清除(Mark and Sweep)、复制(Copying)、训练(Train)是什么意思?


4
不是的,可能只是因为我那样说了而已。无论如何。 - S M Kamran
1
我建议阅读保罗·R·威尔逊(1992年)的《单处理器垃圾回收技术》这篇相当不错的、34页的插图论文,链接在此:http://www.cse.nd.edu/~dthain/courses/cse40243/spring2006/gc-survey.pdf。该论文解释了基本垃圾回收技术(引用计数、标记-清除、标记-整理、增量、分代)背后的概念。 - stakx - no longer contributing
5个回答

42
当你使用带有垃圾回收的语言时,你将无法直接访问内存。相反,你被赋予了一些抽象层来处理数据。其中一个被适当地抽象化的内容是数据块的实际内存位置以及指向其他数据块的指针。当垃圾回收器运行时(这会不时地发生),它会检查你是否仍然持有对其分配给你的每个内存块的引用。如果你没有,它将释放该内存。
不同类型的垃圾回收器之间的主要区别在于它们的效率以及它们能够处理哪种分配方案的任何限制。
最简单的是正确的引用计数。每当你创建一个对象的引用时,该对象上的内部计数器就会增加;当你更改引用或它不再在作用域内时,目标对象上的计数器会减少。当此计数器达到零时,该对象完全没有被引用,并且可以被释放。
引用计数垃圾回收器的问题在于它们不能处理循环数据。如果对象A引用对象B,而对象B又有某种(直接或间接)引用到对象A,即使链中的所有对象都没有被引用(因此根本无法访问程序),它们也永远不会被释放。
另一方面,标记和清除算法可以处理此问题。标记和清除算法通过定期停止程序的执行,并将程序分配的每个项标记为不可达来工作。然后,程序运行所有变量并标记它们指向的内容为可达。如果这些分配中的任何一个包含对程序中的其他数据的引用,则相应的数据也会被标记为可达等等。
这是算法的标记部分。此时,程序可以访问所有间接的内容都被标记为可达,程序无法访问的所有内容都被标记为不可达。垃圾回收器现在可以安全地回收标记为不可达的对象所关联的内存。
标记和清除算法的问题在于它效率不高,必须停止整个程序才能运行它,并且许多对象引用不会发生更改。为了提高效率,可以在标记-清除算法上使用所谓的“分代垃圾回收”技术。在这种模式下,系统中存在一段时间并经历了多次垃圾回收的对象将被提升到老年代,老年代不会被频繁检查。
这种方法提高了效率,因为对象往往要么短命(比如一个字符串在循环中被改变,只存在几百个周期),要么长寿(比如用于表示应用程序主窗口或servlet的数据库连接的对象)。
更详细的信息可以在维基百科上找到。
根据评论添加:
使用标记-清除算法(以及除引用计数之外的任何垃圾回收算法),垃圾回收器并不运行在程序的上下文中,因为它必须能够访问程序无法直接访问的内容。因此说垃圾收集器运行在堆栈上是不正确的。

4
这里有一个问题,你说标记清除法会检查程序中的所有变量。如果我没记错,引用存在于栈上,对象在堆上,那么我们如何将GC过程与堆联系起来呢? 答:你所说的没错,引用确实存在于栈上,而对象存在于堆上。但是,在Java中,GC过程主要针对的是堆上的对象进行回收,因为堆上的对象是由new操作符申请的内存,而栈上的引用只是指向堆上对象的指针。因此,GC过程通常被认为是与堆相关联的。 - S M Kamran

5
垃圾回收就是要知道程序中是否有未来需要使用的变量,如果没有,就将其收集并删除。
重点在于“垃圾”这个词,房子里完全用完的东西被扔进垃圾桶,垃圾车会来把它拿走,为你的垃圾桶腾出更多空间。
参考计数、标记和清除、复制、训练等详细讨论可以在GC FAQ找到。

5
  • 引用计数 - 每个对象都有一个计数器,当有人引用该对象时计数器会增加,当有人释放该对象的引用时计数器会减少。 当引用计数为零时,对象将被删除。 COM使用此方法。
  • 标记清除 - 每个对象都有一个标志表示是否正在使用。 从对象图的根(全局变量,堆栈上的本地变量等)开始,每个引用的对象都设置其标志,依此类推。 最后,将删除图中未被引用的所有对象。

CLR的垃圾收集器在这个幻灯片中进行了描述。 第15页的“根”是最初进入图形中的对象的来源。 使用它们的成员字段等来查找图中的其他对象。

维基百科以更多更好的细节描述了这些方法的几种。


我已经查阅了维基百科...实际上困扰我的是对象图如何被垃圾回收例程维护和遍历。 - S M Kamran
我的答案已更新,包括构建对象图的10k视图。 - Michael

0
通常的做法是在后台跟踪对象的引用数量,当该数字降至零时,对象将被标记为垃圾回收,但由于这是一项昂贵的操作,因此垃圾回收器不会立即启动,直到明确需要为止。当它开始工作时,垃圾回收器会遍历受管理的内存区域,并找到所有没有剩余引用的对象。垃圾回收器通过首先调用它们的析构函数来删除这些对象,允许它们自我清理,然后释放内存。通常,垃圾回收器会将每个幸存对象移动到一个内存区域中,从而使更多的分配可以发生,然后压缩受管理的内存区域。
就像我说的,这是我知道的一种方法,目前在这个领域有很多研究正在进行。

0

垃圾回收是一个大的话题,有很多实现方式。

但对于最常见的而言,垃圾收集器会记录通过new操作符创建的所有引用,即使该操作符的使用对您来说是隐藏的(例如,在Type.Create()方法中)。每次添加新引用到对象时,该引用的被确定并添加到列表中(如果需要)。当引用超出范围时,引用将被删除。

当没有更多引用指向一个对象时,它可以(不是“将”)被回收。为了提高性能并确保必要的清理正确完成,收集会批处理多个对象,并在多个代中进行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接