垃圾收集算法涉及大量的程序细节,各平台的虚拟机操作内存的方法又各不相同,故而本篇只介绍几种算法的思想及其发展过程。
一、标记-清除算法(Mark-Sweep)
最基础的收集算法(后续算法都是以它的不足而改进得到,所以称最基础),分为”标记“和”清除“两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
-
不足
- 效率问题,标记有清除两个过程的效率都不高。
- 空间问题,标记清除之后会产生大量不连续的空间碎片,碎片太多会导致以后在程序运行过程中需分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收收集动作。
-
图解
标记清除算法.png
二、复制算法(Copying)
为解决效率问题,复制(Copying)收集算法出现:它将内存按容量划分为大小相同的两块,每次只使用其中的一块。当一块内存用完了,就将还存活的对象复制到另外一块上面,然后再把以使用过的内存空间一次性清理掉。
这样使得每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片化等复杂情况,只要移动堆顶指针,将顺序分配内存即可,实现简单,运行高效。
-
不足
- 这种算法的代价是将内存缩小为了原来的一半,未免太高了点。
- 随着存活对象增多,Copying算法的效率会大大减低。
-
图解
复制算法.png
-
使用
现在商业JVM都采用复制算法回收新生代。IBM研究表明,新生代中98%对象”朝生夕死“的,故而不需按照1:1的比例来划分空间内存。HotSpot虚拟机默认下,Eden区:From Survivor区:To Survivor区为 8:1:1,新生代总可用空间为Eden区+1个Survivor区的总容量,即90%,只有10%会被浪费。但也不保证每次回收都只有不足10%的对象存活,当Survivor 空间不够用时,老年代进行分配担保(即这些对象直接通过分配担保机制进入老年代)。
三、标记-整理算法(Mark-Compact)
根据老年代的特点,“标记-整理”(Mark-Compact)算法适时出现,标记过程仍然和"标记-清除"算法中一致,但是后续过程是让所有存活对象移至一端,然后直接清理掉端边界外的内存。
-
图解
标记整理算法.png
四、分代收集算法(Generational Collection)
目前大部分虚拟机都采用“分代收集”(Generational Collection)算法,他根据对象存活周期的不同,将内存划分为新生代和老年代,并采取不同的收集算法回收对象。
-
新生代(复制算法)
新生代每次GC都会回收大量对象,存活较少,就算用复制算法。将新生代划分为一块较大的Eden区和两块较小的Survivor空间(From Survivor、To Survivor)。
第一步:把 Eden 和From Survivor区域中存活的对象复制到 To Survivor 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 To Survivor 不够位置了,就通过分配担保机制提前转移到老年区);
第二步:清空Eden、From Survivor中的对象;
第三步:From Survivor与To Survivor位置互换,原先的To Survivor成为下一次GC的From Survivor区域(From Survivor作为)。
堆内存.png
-
老年代(标记-清楚算法或标记-整理算法)
老年代中对象存活率高、没有额外空间进行分配担保,因而采用标记-清除或者标记-整理算法。
分区收集算法
分区收集算法则将整个堆空间划分为连续的不同小区间,每个小区间独立使用,独立回收。这样做可以控制一次回收多少个小区间,根据目标提顿时间,每次合理地回收若干个小区间(而不是整个堆),从而减少一次GC所产生的停顿。
网友评论