JVM垃圾收集算法

分代收集理论

垃圾收集区域

 部分收集(Partial GC):
       新生代收集(Minor GC/Young GC):
       老年代收集(Major GC/Old GC):
       混合收集(Mixed GC):
 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集

标记-清除算法

       最早出现也是最基础的垃圾收集算法。算法分为"标记" 和 "清除" 两个阶段: 首先标记初所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。标记的过程就是对象是否属于垃圾的判定过程。

主要缺点

执行效率不稳定

       如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低。

内存空间碎片化问题

       标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后程序运行过程中需要分配较大对象时无法找到足够的连续空间而不得不提前触发另一次垃圾收集动作。

标记-清除算法示意图

标记-复制算法

目的:解决前者执行效率低的问题

       为了解决 标记-清除 算法面对大量可回收对象时执行效率低的问题。1969年 Fenichel 提出一种称为"半区复制"(Semispace Copying)的垃圾收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象复制
 到另一块上面,然后再把已使用过的内存空间一次性清理掉。

缺点:内存缩小一半,空间浪费

优化后 Appel 式回收

       HotSpot虚拟机的Serial、ParNew 等新生代收集器均采用这种策略来设计新生代的内存布局。
       Appel式回收的具体做法式把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发送垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Surivor空间上,然后直接清理掉Eden和已用过的那块Survivor
 空间。HotSpot虚拟机默认Eden和Survivor的大小比例时8:1:1。当Survivor空间不足以容纳一次Minor GC之后存货的对象时,就需要依赖其他内存区域进行分配担保(Handle Promotion)

标记复制算法示意图

分配担保

       在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC可以确保是安全的。如果不成立,则虚拟机会检查看 -XX:HandlePromotionFailure 参数的设置值是否允许担保失败(Handle 
 Promotion Failure);如果允许,那会继续检查老年代最大可以用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试一次Minor GC,尽管这次GC是有风险的;如果小于,或者参数设置false,哪就要进行一次Full GC。

分配担保视图

标记-整理算法

       应对被使用的内存中所有对象都100%存活的极端情况,老年代一般采用 "标记-整理"(Mark - Compact)算法,先标记,然后让所有存活对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
       标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,后者是移动式的。

是否移动回收后存活的对象是一项优缺点并存的风险决策

如果移动存活对象

       如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将是一种极为负重的操作,并且这种对象移动操作必须全程暂停用户应用程序才能进行。

如果不移动和整理存活对象

       如果完全不考虑移动和整理存活对象的话,弥散与堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决。譬如通过"分区空闲分配链表"来解决内存分配问题。会额外给内存增加负担,影响应用程序的吞吐量。

标记-整理算法

原文地址:https://www.cnblogs.com/huan30/p/14326506.html