「译文」Java 垃圾收集参考手册(三):GC 算法基础篇
本文最后更新于:2024年7月25日 下午
相关术语翻译说明:
Mark, 标记;
Sweep, 清除;
Compact, 整理;也有人翻译为压缩,译者认为 GC 时不存在压缩这回事。
Copy, 复制;copy 用作名词时一般翻译为拷贝 / 副本,用作动词时翻译为复制。
注: 《垃圾回收算法手册》将 Mark and Sweep 翻译为: 标记 - 清扫算法;译者认为 标记 - 清除 更容易理解。
GC 算法基础
您应该已经阅读了前面的章节。
本章简要介绍 GC 的基本原理和相关技术,下一章节 再详细讲解 GC 算法的具体实现。各种垃圾收集器的实现细节虽然并不相同,但总体而言,垃圾收集器都专注于两件事情:
- 查找所有存活对象
- 抛弃其他的部分,即死对象,不再使用的对象。
第一步,记录 (census) 所有的存活对象,在垃圾收集中有一个叫做 标记 (Marking) 的过程专门干这件事。
标记可达对象 (Marking Reachable Objects)
现代 JVM 中所有的 GC 算法,第一步都是找出所有存活的对象。下面的示意图对此做了最好的诠释:
首先,有一些特定的对象被指定为 Garbage Collection Roots(GC 根元素)。包括:
- 当前正在执行的方法里的局部变量和输入参数
- 活动线程 (Active threads)
- 内存中所有类的静态字段 (static field)
- JNI 引用
其次,GC 遍历 (traverses) 内存中整体的对象关系图 (object graph), 从 GC 根元素开始扫描,到直接引用,以及其他对象 (通过对象的属性域)。所有 GC 访问到的对象都被 ** 标记 (marked)** 为存活对象。
存活对象在上图中用蓝色表示。标记阶段完成后,所有存活对象都被标记了。而其他对象 (上图中灰色的数据结构) 就是从 GC 根元素不可达的,也就是说程序不能再使用这些不可达的对象 (unreachable object)。这样的对象被认为是垃圾,GC 会在接下来的阶段中清除他们。
在标记阶段有几个需要注意的点:
在标记阶段,需要暂停所有应用线程,以遍历所有对象的引用关系。因为不暂停就没法跟踪一直在变化的引用关系图。这种情景叫做 Stop The World pause (全线停顿), 而可以安全地暂停线程的点叫做安全点 (safe point), 然后,JVM 就可以专心执行清理工作。安全点可能有多种因素触发,当前,GC 是触发安全点最常见的原因。
此阶段暂停的时间,与堆内存大小,对象的总数没有直接关系,而是由存活对象 (alive objects) 的数量来决定。所以增加堆内存的大小并不会直接影响标记阶段占用的时间。
标记 阶段完成后,GC 进行下一步操作,删除不可达对象。
删除不可达对象 (Removing Unused Objects)
各种 GC 算法在删除不可达对象时略有不同,但总体可分为三类:清除 (sweeping)、整理 (compacting) 和复制 (copying)。下一章节 将详细讲解这些算法。
Sweep (清除)
Mark and Sweep (标记 - 清除) 算法的概念非常简单:直接忽略所有的垃圾。也就是说在标记阶段完成后,所有不可达对象占用的内存空间,都被认为是空闲的,因此可以用来分配新对象。
这种算法需要使用 空闲表 (free-list), 来记录所有的空闲区域,以及每个区域的大小。维护空闲表增加了对象分配时的开销。此外还存在另一个弱点 —— 明明还有很多空闲内存,却可能没有一个区域的大小能够存放需要分配的对象,从而导致分配失败 (在 Java 中就是 OutOfMemoryError)。
Compact (整理)
标记 - 清除 - 整理算法 (Mark-Sweep-Compact), 将所有被标记的对象 (存活对象), 迁移到内存空间的起始处,消除了标记 - 清除算法的缺点。 相应的缺点就是 GC 暂停时间会增加,因为需要将所有对象复制到另一个地方,然后修改指向这些对象的引用。此算法的优势也很明显,碎片整理之后,分配新对象就很简单,只需要通过指针碰撞 (pointer bumping) 即可。使用这种算法,内存空间剩余的容量一直是清楚的,不会再导致内存碎片问题。
Copy (复制)
标记 - 复制算法 (Mark and Copy) 和 标记 - 整理算法 (Mark and Compact) 十分相似:两者都会移动所有存活的对象。区别在于,标记 - 复制算法是将内存移动到另外一个空间:存活区。标记 - 复制方法的优点在于:标记和复制可以同时进行。缺点则是需要一个额外的内存区间,来存放所有的存活对象。
原文链接: GC Algorithms: Basics
翻译人员:铁锚 http://blog.csdn.net/renfufei
翻译时间: 2016 年 02 月 06 日
GC 算法概述
学习了 GC 算法的相关概念之后,我们将介绍在 JVM 中这些算法的具体实现。首先要记住的是,大多数 JVM 都需要使用两种不同的 GC 算法 —— 一种用来清理年轻代,另一种用来清理老年代。
我们可以选择 JVM 内置的各种算法。如果不通过参数明确指定垃圾收集算法,则会使用宿主平台的默认实现。本章会详细介绍各种算法的实现原理。
下面是关于 Java 8 中各种组合的垃圾收集器概要列表,对于之前的 Java 版本来说,可用组合会有一些不同:
Young | Tenured | JVM options |
---|---|---|
Incremental (增量 GC) | Incremental | -Xincgc |
Serial | Serial | -XX:+UseSerialGC |
Parallel Scavenge | Serial | -XX:+UseParallelGC -XX:-UseParallelOldGC |
Parallel New | Serial | N/A |
Serial | Parallel Old | N/A |
Parallel Scavenge | Parallel Old | -XX:+UseParallelGC -XX:+UseParallelOldGC |
Parallel New | Parallel Old | N/A |
Serial | CMS | -XX:-UseParNewGC -XX:+UseConcMarkSweepGC |
Parallel Scavenge | CMS | N/A |
Parallel New | CMS | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
G1 | -XX:+UseG1GC |
看起来有些复杂,不用担心。主要使用的是上表中黑体字表示的这四种组合。其余的要么是被废弃 (deprecated), 要么是不支持或者是不太适用于生产环境。所以,接下来我们只介绍下面这些组合及其工作原理:
- 年轻代和老年代的串行 GC (Serial GC)
- 年轻代和老年代的并行 GC (Parallel GC)
- 年轻代的并行 GC (Parallel New) + 老年代的 CMS (Concurrent Mark and Sweep)
- G1, 负责回收年轻代和老年代