垃圾收集算法
由于垃圾收集算法的实现涉及大量的程序细节,而且各个平台的虚拟机操作内存的方法又各不相同,因此,本节不打算过多的讨论算法的实现,只介绍几种算法的思想。
一、标记 - 清除算法
算法思想:
“标记 - 清除”(Mark-Sweep)算法,分为 “标记” 和 “清除” 两个阶段:
- 首先标记出所有需要回收的对象,标记过程其实就是上一节回收对象的标记判定方式;
- 在标记完成后统一回收所有被标记的对象。
标记 - 清除算法是最基础的收集算法,因为后面的收集算法都是基于这种思路并对其不足进行改进而得到的。
两个不足:
- 一个是效率问题,标记和清除两个过程的效率都不高;
- 另一个是空间问题,标记清除后会产生大量不连续的内存碎片。空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
为了解决效率问题,一种称为 “复制”(Copying)的收集算法出现了。
二、复制算法
算法思想:
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
- 这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。实现简单,运行高效。
适用对象:常采用这种算法来回收新生代。
用这种算法回收新生代原因(如何回收新生代内存):
一般情况下新生代中的对象 98% 是“朝生夕死”,HotSpot 就将新生代内存分为一块较大的 Eden空间 和 两块较小的Survivor空间。每次使用 Eden 和其中一块 Survivor 。这是不是和复制算法的思想挺像呢。
当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才使用过得Survivor空间。如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保(Handle Promotion)机制进入老年代。
HotSpot虚拟机默认Eden 和 一块Survivor的大小比例是 8 :1,也就是每次新生代中可用内存空间为整个新生代容量的 90%(80%+10%),只有10%的内存会被“浪费”。所以这里并没有按照复制算法 1:1的比例来划分内存空间。
不足:在对象存活率较高时就要进行较多的复制操作,效率将会变低。极端情况是在使用的内存中所有对象都 100% 存活,就需要额外的空间进行分配担保。
三、标记 - 整理算法
算法思想:
分为 “标记” 和 “整理” 两个过程:
- 标记过程仍与“标记 - 清除”算法一样;
- 再让所有存活的对象都向一端移动,然后直接清理掉排在存活对象后面的可回收的内存。
适用对象:“标记 - 整理”(Mark-Compact)算法适用于回收老年代内存。
四、分代收集算法
当前商业虚拟机的垃圾收集都采用 “分代收集”(Generational Collection)算法。
算法思想:
- 根据对象存活周期的不同将内存划分为几块。一般是把 Java堆分为 新生代 和 老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
- 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
- 而老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用 “标记—清理” 或者 “标记—整理” 算法来进行回收。
总结:
Java堆分为 新生代 和 老年代。
新生代内存分为一块较大的 Eden空间 和 两块较小的Survivor空间。每次使用 Eden 和其中一块 Survivor 。HotSpot虚拟机默认Eden 和 一块Survivor的大小比例是 8 :1。
新生代特点:一般情况下 98% 的新生代对象 是“朝生夕死”,每次垃圾收集只有少量存活。
老年代特点:对象存活率高、没有额外空间对它进行分配担保。
现在的收集器基本都采用分代收集算法。
如有错误,欢迎留言指正 * _ *