JVM之Java垃圾收集算法
[TOC]
JVM之Java垃圾收集算法
标记清除算法
分为两部分:“标记”和“清除”。首先标记出所有需要回收的对象,在标记完成之后,统一回收掉所有被标记的对象;也可以反过来,标记存活的对象,统一回收未标记的对象。标记过程就是对象是否属于垃圾的判定过程。
标记清除算法有两个主要的缺点:
- 执行效率不稳定,如果Java堆中包含大量对象,而且大部分都是需要回收的,这是就必须进行大量的标记清除动作,导致标记和清除这两个过程的执行效率都随着对象数量的增长而降低。
- 内存空间碎片化的问题,标记清除之后会有大量不连续的内存碎片,空间碎片太多可能会导致之后程序在运行时需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
算法执行过程如图:
标记复制算法
又称复制算法。该算法将内存分为两块相等大小的区域,每次使用一块,一块内存使用完之后,就将存活的对象复制到另一块内存区域上,然后将这一整块已经使用过的内存给整体清理掉。复制算法虽然解决了标记清除算法产生碎片化空间的问题,但是他也有两个缺点:
- 因为复制算法是复制的存活对象,如果存活的对象占大多数,就会有一大笔复制对象的开支
- 复制算法将内存分为两块,每次只使用一块,另一块则不使用,相当于将可用内存缩小为原来的一半,产生了大量的空间浪费
算法执行过程图:
在1989年,Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策 略,现在称为“Appel式回收”。HotSpot虚拟机的Serial、ParNew等新生代收集器均采用了这种策略来设 计新生代的内存布局。Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍 然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空 间。
HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新 生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会 被“浪费”的。当然,98%的对象可被回收仅仅是“普通场景”下测得的数据,任何人都没有办法百分百 保证每次回收都只有不多于10%的对象存活,因此Appel式回收还有一个充当罕见情况的“逃生门”的安 全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实 际上大多就是老年代)进行分配担保(Handle Promotion)。
标记整理算法
该算法的标记阶段与“标记清除算法”一样,但之后不是直接对对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
算法执行过程图:
标记整理算法与标记清除算法的差异在于前者是一种移动式的算法,而后者式非移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策:
如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新 所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用 程序才能进行,这就更加让使用者不得不小心翼翼地权衡其弊端了,像这样的停顿被最初的虚拟机 设计者形象地描述为“Stop The World” 。
但如果跟标记-清除算法那样完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的 空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决。