关于内存管理:垃圾收集机制如何工作?

How does the Garbage Collection mechanism work?

用外行术语来说,垃圾收集机制是如何工作的?

如何确定对象可用于垃圾收集?

另外,在GC算法中,Reference Counting, Mark and Sweep, Copying, Train是什么意思?


当您使用垃圾收集语言时,您将无法直接访问内存。相反,您可以访问这些数据之上的一些抽象。正确抽象掉的内容之一是数据块内存中的实际位置,以及指向其他数据块的指针。当垃圾收集器运行时(偶尔会发生这种情况),它将检查您是否仍然持有对它为您分配的每个内存块的引用。如果你不这样做,它会释放你的记忆。

不同类型的垃圾收集器之间的主要区别在于它们的效率以及它们可以处理的分配方案类型的任何限制。

最简单的是正确的参考计数。当您创建对对象的引用时,该对象上的内部计数器将递增,当您偶然看到该引用或它不再在作用域中时,(以前的)目标对象上的计数器将递减。当这个计数器达到零时,对象就不再被引用,可以被释放。

引用计数垃圾收集器的问题是它们不能处理循环数据。如果对象A引用了对象B,而对象A又有一些(直接或间接)引用,则永远无法释放它们,即使链中的任何对象都没有被引用到链外(因此程序根本无法访问)。

另一方面,标记和扫描算法可以处理这个问题。标记和扫描算法的工作原理是定期停止程序的执行,将程序分配的每个项目标记为不可访问。然后,程序运行程序拥有的所有变量,并将它们指向的内容标记为可访问。如果这些分配中的任何一个包含对程序中其他数据的引用,那么该数据也同样被标记为可访问等。

这是算法的标记部分。此时,程序可以访问的所有内容,无论多么间接,都被标记为可访问,而程序无法访问的所有内容都被标记为不可访问。垃圾收集器现在可以安全地回收与标记为不可访问的对象相关联的内存。

Mark and Sweep算法的问题在于它没有那么高效——必须停止整个程序才能运行它,而且很多对象引用不会改变。

为了改进这一点,标记和扫描算法可以扩展为所谓的"世代垃圾收集"。在此模式下,系统中已存在一定数量垃圾收集的对象将提升为旧代,而旧代则不会经常检查。

这提高了效率,因为对象往往很年轻(想想一个字符串在一个循环中被更改,可能会导致几百个循环的生命周期)或寿命很长(用于表示应用程序主窗口或servlet的数据库连接的对象)。

更多的详细信息可以在维基百科上找到。

根据评论添加:

使用标记和扫描算法(以及除引用计数之外的任何其他垃圾收集算法),垃圾收集不会在程序上下文中运行,因为它必须能够访问程序无法直接访问的内容。因此,说垃圾收集器在堆栈上运行是不正确的。


  • 引用计数-每个对象都有当有人提到当某人释放引用。当引用计数为零时,对象将被删除。com使用这种方法。
  • 标记和扫描-每个对象在使用时都有一个标记。从对象图的根(全局变量、堆栈上的局部变量等)开始,每个被引用的对象都会得到它的标志集,以此类推。最后,删除图中未引用的所有对象。

此幻灯片中描述了CLR的垃圾收集器。"幻灯片15中的"根"是第一个进入图表的对象的源。它们的成员字段等用于查找图形中的其他对象。

维基百科对这些方法的描述越来越详细。


垃圾收集只是知道程序中是否将来需要变量,如果不需要,收集并删除它们。

重点是"垃圾"这个词,一些在你家里完全用完的东西被扔进垃圾桶,垃圾工来帮你处理,把它捡起来带走,给你家垃圾桶更多的空间。

在GC FAQ中详细讨论了参考计数、标记和扫描、复制、训练等。


垃圾收集是一个大主题,有很多方法可以实现它。

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

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


一般的方法是在后台跟踪对象的引用数量,当该数量变为零时,对象将受到垃圾收集的影响,但是,除非显式需要,否则GC不会启动,因为这是一个昂贵的操作。当GC启动时,它会经过内存的托管区域,并查找没有剩余引用的每个对象。GC通过首先调用析构函数删除这些对象,允许它们在自己之后进行清理,然后释放内存。通常,GC会通过将每个存活的对象移动到内存的一个区域来压缩托管内存区域,从而允许进行更多的分配。

就像我说的,这是我知道的一种方法,在这方面有很多研究正在进行。