一、分代收集理论
分代收集理论建立在两个分代假说之上:
- 弱分代假说:绝大多数对象都是朝生夕灭的。
- 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。
这两个分代假说共同奠定了多款常用垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。
- 跨代引用假说:存在互相引用关系的两个对象是应该倾向于同时生存或者同时消亡的。比如:某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生代对象在收集时同样得以存活。
依据这条假说,当发生Minor GC时,只有包含了跨代引用的老年代中的小块内存里的对象才会被加入到GC Roots进行扫描。
名词解释
- 部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集。其中又分为:
- 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
- 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。
- 混合收集(Mixed GC):指目标是收集整个新生代和部分老年代的垃圾收集。
- 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
二、标记—清除算法
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
缺点
这个算法的主要缺点有两个:
- 执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低。
- 内存空间的碎片化问题。标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
三、标记—复制算法
算法过程:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上,然后再把已使用过的内存空间一次性清理掉。
优点
- 没有内存空间的碎片化问题。
- 分配内存时,只要移动栈顶指针,按顺序分配即可。这样实现简单,运行高效。
缺点
- 将可用内存缩小为了原来的一半,空间浪费太多了。
- 执行效率不稳定:如果内存中多数对象是存活的,将会产生大量的内存间复制的开销,效率就会降低。
Apple式回收
现在的商用Java虚拟机大多都优先采用了这种收集算法去回收新生代。
新生代的对象中有98%都具有“朝生夕灭”的特点,针对这一特点,有一种更优化的半区复制分代策略,现在称为“Apple式回收”。
Apple式回收的具体做法是:把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中依然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。
HotSpot虚拟机默认Eden和两个Survivor的大小比例是8 :1 :1。
逃生门
Apple式回收还有一个充当“逃生门”的安全设计:当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保。
四、标记—整理算法
算法分为“标记”和“整理”两个阶段:首先标记出所有需要回收的对象,在标记完成后,让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
针对老年代对象的死亡特征,一般都采用这种算法。
缺点
如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活的区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行。
HotSpot虚拟机里面关注吞吐量的Parallel Old收集器是基于标记—整理算法的,而关注延迟的CMS收集器则是基于标记—清除算法的。