翻译自《TheGarbageCollectionHandbook》第二章:Mark-sweep garbage collection
所有垃圾收集都是基于以下四种之一的基本方法而设计的(标记-清除收集mark- sweep,复制收集copying,标记-整理收集mark-compact,引用计数reference counting)。不同的收集器可以通过不同方式组合这些方法。例如:用一个方法收集堆的一个区域,用另一个方法收集堆的其他部分。接下来的四章将重点介绍这四种基本收集类型。在第六章我们将比较他们的特点。
现在我们假设mutator(增变器,简单参考了下网上的解释【mutator是EdsgerDijkstra琢磨出来的词,有“改变某物”的意思。说到要改变什么,那就是GC对象间的引用关系。不过光这么说可能大家还是不能理解,其实用一句话概括的话,它的实体就是“应用程序”。这样说就容易理解了吧。GC就是在这个mutator内部精神饱满地工作着】)运行一个或多个线程而收集器线程只有一个。当收集器线程开始运行时,所有应用线程都将停止,这种STW(stop-the-world)的方法极大的简化了收集器的构造。从应用线程的角度看,收集过程看似是原子性:应用线程无法看到任何收集线程的中间态,收集器也无法看到应用线程对其任务的干预。我们假设每一个线程都停止在一个安全点(这个点用于检查根):我们将在第十一章查看运行时接口的详情。STW时提供了一个堆的快照,所以我们不用担心收集器试图确认对象是否存活时,应用程序重新改变堆中对象的拓扑结构的情况。这也意味着当线程释放空间或使用分配器试图获取空间时,没必要同步其他收集线程。在第七章前,我们尽量避免多个应用线程如何获取新内存的问题。还有更多复杂的运行时系统使用并行收集器线程或者允许应用线程与收集器线程同时执行。我们将在稍后的章节讨论。
我们鼓励读者在进入后面几节更高级的收集器学习之前先熟悉接下来的四章收集器的学习。尽管我们希望通过实现这些收集器的复杂叙述来引起读者兴趣,但有经验的读者可能希望跳过基本算法的介绍。我们建议那些想要查阅资料的读者能够阅读这4章被浓缩的精华,其中包含了很多例子来详细的解释这些经典算法。
一个理想的垃圾收集器的目标是回收每一个不在被程序中使用的对象,任意一个自动内存管理系统都有三个任务:
1. 为新对象分配空间
2. 确认存活对象
3. 回收无效对象占用的空间
这些任务不是独立的,尤其是,回收空间的方式会影响如何对新空间的分配。正如我们第一章所说,空间是否有活力是一个无法决定的问题,取而代之,我们将问题转换成近似的存活对象的集合问题上:指针可达性(在第13页定义)。我们接受一个对象是存活的,当且仅当该对象可以被已知的根集合的引用链到达。引申开来,如果一个对象不是存活的,它不能通过任何这样的指针链到达,则他所占用的空间将被回收。这是一个安全的设计,尽管一些对象在存活集合中无法被再次访问,所有那些在非存活集合中的对象确定是不存活的。
我们介绍的第一个算法是标记-清除收集算法。这是递归定义指针可达性的直接体现。收集操作分为两步:第一,收集器从根(寄存器、线程栈、全局变量)开始遍历程序可以立即访问到的对象的图,然后跟随指针并标记他找到的每一个对象。这样的遍历被称为跟踪。第二步,清除步骤。收集器检查堆中的每一个对象。任意未被标记的对象均被视为垃圾并且回收他的空间。
标记-清除是一个间接的收集算法,他本身不检测垃圾,而是确认所有存活对象,然后得出结论:其他任何对象都是垃圾。注意,每一次集合中的对象被调用时,他都需要重新计算集合中的预估数量。不是所有的垃圾收集算法会这样的行为。第五章讨论了一种直接收集算法——引用计数。与间接方法不同,直接算法根据对象本身决定一个对象的是否存活,不依赖于跟踪。