前言
前几天面试的时候有面试官问我:为什么 Major GC 比 Minor GC 慢很多?
原因如下:
- 新生代内存空间比老年代小。
- 新生代和老年代采用的垃圾收集算法不同,即新生代采用的 Copying 算法比老年代采用的 Mark-Sweep 算法耗时更短。
Major GC, Minor GC, 新生代, 老年代的概念请看:http://xiazdong.me/2015/01/03/jvm-memory-model/。
原理分析
- 新生代一般采用 Copying 算法进行垃圾收集。
- 老年代一般采用 Mark-Sweep 或 Mark-Compact 或两者组合的方式(定期进行 Compact)进行垃圾收集。
Mark-Sweep 算法
这个算法共有两个步骤:
- Mark: 从 GC Roots 开始遍历可达的所有节点,并对其标记。因此时间复杂度为存活对象的个数。
- Sweep: 对整个老年代内存进行遍历,并将没标记的内存块进行清理。
伪代码如下:
|
Mark-Sweep 算法的优点是简单方便,缺点是造成内存碎片。
Mark-Compact 算法
由于 Mark-Sweep 算法会导致内存碎片化,因此就引入了 Mark-Compact 算法,该算法执行完毕后,存活的对象会按序放置,但是执行时间较长。
Mark-Compact 是 时间换空间,Copying 是 空间换时间,他和 Copying 算法最后都是将存活的对象整理,但是 Mark-Compact 由于要在原有内存上做 Compact(空间更小),因此耗费的时间比 Copying 时间更长(时间更长)。
这个算法共两个步骤:
- Mark: 从 GC Roots 开始遍历可达的所有节点,并对其标记。因此时间复杂度为存活对象的个数。
- Compact: 对整个老年代内存进行遍历,并将没标记的内存块进行清理。
伪代码如下:
|
Copying 算法
Copying 算法是新生代采用的垃圾收集算法,他将新生代内存分成两块(或多块),每次只使用其中的一块。因此浪费了一倍的内存空间,但是由于新生代的内存空间相比老年代较小,因此浪费一倍也还好。
该算法就是 Mark-Sweep 算法的 Mark 阶段:遍历一遍 GC Roots 可达的所有节点(假设这些节点都在块1),但是在遍历节点的同时,将该节点按序复制到新生代的另一块内存(块2)。当遍历完所有可达节点后,一次性将块1的内存清理干净。
伪代码如下:
|
Copying 算法的复杂度依赖于存活对象的个数,如果存活的对象太多,那么复制操作会占用很长时间,但是由于新生代具有存活对象很少的特点,因此该算法适用于新生代的垃圾收集。
Copying 算法的优点是一次遍历即可完成,缺点是如果存活对象很多,那么复制操作会很耗时。
各算法时间比较
上面介绍了 Mark-Sweep, Mark-Compact, Copying 三种算法,总体来说可以归纳为:
- Mark-Sweep = Mark 操作 + Sweep 操作。
- Mark-Compact = Mark 操作 + Compact 操作。
- Copying = Mark 操作 + Copy 操作。
这些操作的耗时比较:
- Compact > Copy > Mark > Sweep
- Mark + Sweep > Copy