当您使用带有垃圾收集的语言时,您将无法直接访问内存。相反,您可以访问该数据之上的一些抽象。正确抽象的一件事是数据块在内存中的实际位置,以及指向其他数据块的指针。当垃圾收集器运行时(这种情况偶尔会发生),它会检查您是否仍然持有对它为您分配的每个内存块的引用。如果不这样做,它将释放该内存。
不同类型的垃圾收集器之间的主要区别在于它们的效率以及它们可以处理的分配方案类型的任何限制。
最简单的是正确的引用计数。当您创建对对象的引用时,该对象上的内部计数器会增加,当您偶然引用或它不再在范围内时,(前)目标对象上的计数器会减少。当这个计数器达到零时,对象就不再被引用并且可以被释放。
引用计数垃圾收集器的问题在于它们无法处理循环数据。如果对象 A 具有对对象 B 的引用,而对象 B 又具有对对象 A 的某些(直接或间接)引用,则它们永远不能被释放,即使链中的任何对象都没有在链外被引用(因此不是程序根本无法访问)。
另一方面,标记和扫描算法可以处理这个问题。标记和清除算法通过定期停止程序的执行来工作,将程序分配的每个项目标记为不可达。然后程序运行程序具有的所有变量并将它们指向的变量标记为可达。如果这些分配中的任何一个包含对程序中其他数据的引用,则该数据同样被标记为可访问等。
这是算法的标记部分。此时所有程序可以访问的东西,无论多么间接,都被标记为可达,而程序无法访问的所有东西都被标记为不可达。垃圾收集器现在可以安全地回收与标记为无法访问的对象关联的内存。
标记和清除算法的问题在于它效率不高——必须停止整个程序才能运行它,而且很多对象引用不会改变。
为了改进这一点,标记和清除算法可以扩展为所谓的“分代垃圾收集”。在这种模式下,已经在系统中进行了一定数量的垃圾回收的对象会被提升到老年代,而不是经常检查。
这提高了效率,因为对象往往会在年轻时死去(想想在循环中更改字符串,可能会导致几百个循环的生命周期)或寿命很长(用于表示应用程序主窗口的对象, 或 servlet 的数据库连接)。
更多详细信息可以在维基百科上找到。
基于 cmets 添加:
使用标记和清除算法(以及除引用计数之外的任何其他垃圾收集算法),垃圾收集不会在您的程序上下文中运行,因为它必须能够访问您的程序无法直接访问的东西。所以说垃圾收集器在栈上运行是不正确的。