垃圾收集算法
标记-清除
标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,回收未标记的对象。
后续的收集算法大多都是以此为基础,对其缺点做优化。
缺点:
1.执行效率不稳定
如果java堆中的对象过多,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
2.内存空间的碎片化问题
标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记-复制
为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,提出标记-复制算法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。分配内存时也就不用考虑有 空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。
优点:
实现简单,运行高效
缺点:
可用内存缩小为了原来的一半,产生了不小的空间浪费
优化 -Appel式回收
把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden:Survivor:Survivor的大小比例是8∶1:1,也即每次新生代中可用内存空间为整个新生代容量的90%。
当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保(Handle Promotion)。这些对象便将通过分配担保机制直接进入老年代
标记-整理
标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。如果出现被使用的内存中所有对象都100%存活,50%的内存是不够的,需要使用分配担保,因此不适合老年代对象的收集。
其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策:
移动回收:
造成stop the world现象(将存活的对象向边界移动,这种对象移动操作必须全程暂停用户应用程序才能进行)
直接回收:
造成空间碎片化问题