在深日讨论垃圾收集算法的实际细节之前,定义清除所需要的术语并且理解算法的基本原则是有好处的。具体的细节会因为收集器的不同而不同,但是所有的收集器都会把注意力放在下面两个方面
- 找出所有还存活的对象
- 清理其他的对象--死亡的和未使用的对象
第一部分会普查所有存活的对象,这个过程在所有的垃圾收集器中被称为标记过程
标记存活对象
JVM中使用的所有GC算法中,找出所有存活的对象都是第一步要做的工作。下图代表的内存布局可以很好的解释这个概念。
首先,GC把一些对象作为GC Roots。JVM中的根对象可以是下列种类
- 本地变量或者当前方法的参数
- 活跃的线程
- 加载类的静态字段
- JNI reference
接下来GC从这些根对象触发,找出内存中所有能从根对象引用到的对象。每个被遍历到的对象都被标记
上图中把存活的对象标记为蓝颜色。当标记阶段完成后,所有的存活对象都被标记。上图中所有灰色的对象认为是从根对象不可达的,这意味者程序再也不能访问这些对象。这样的对象就被认为是垃圾。
标记阶段需要注意的重要方面
- 标记阶段需要停止应用程序的线程,因为如果对象的引用持续变化,GC无法真正的遍历内存中的对象。所以当应用程序线程暂停以便与JVM可以做一些清理活动的过程,这种情况被成为安全点,这也会导致STW阶段。安全点可能有很多原因触发,但是因为垃圾收集进入安全点是最常见的情况。
- 应用程序暂停的时间和堆中对象的数目以及堆的大小无关,而是取决于堆中存活的对象的数量。所以只是增大堆内存不会直接影响程序的暂停时间。
当标记阶段完成后,GC会进入下一步并开始删除不可达的对象。
移除不使用对象
对于不同的GC算法,移除未使用对象的实现都有所不同,但是所有这些GC算法可以分为三个步骤:清除(Sweep),压缩和复制。接下来的部分将更详细地讨论这些算法。
清除(Sweep)
标记-清除算法从概念上讲是最简单的GC算法。此算法仅仅忽略垃圾对象。这意味着在标记阶段完成之后,未访问对象占用的所有空间都被认为是空闲的,因此可以重新用于分配新对象。
Sweep算法使用free-list记录每个空闲的区域和大小。free-list的管理增加对象分配的开销了,这种方法的有另一个弱点--可能存在大量的空闲区域,如果没有单个区域足够大的对象分配,那么就可能出现错误的结果。详情参见OutOfMemoryError

压缩(Compact)
Mark-Sweep-Compact算法通过将所有标记的存活对象移动到集中的区域来解决Mark和Sweep的缺点。这种方法的缺点就是增加了GC时应用程序暂停的时间,因为要花更多的时间移动存活的对象。但是好处也是显而易见的,内存经过压缩后,新对象的分配代价比较低而且不会有碎片化问题。

复制(Copy)
Mark and Copy算法和Mark and Compact算法相似,复制算法也会移动存活的对象。不同于压缩算法的是,复制算法将对象移动到Survivor区域,而不是同一块内存区域。

网友评论