JVM 垃圾收集算法
终于来到了GC收集,这一块比较干。垃圾回收的具体怎么实现,在不同平台的虚拟机可能都有不同。重点说一下 分代收集理论和算法以及发展。
分代收集理论
分代收集理论应该可以算是目前商业虚拟机的垃圾收集器的设计原则。
分代假说:
- 弱分代假说:绝大多数对象是朝生夕灭
- 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
- 跨代引用假说:跨代引用相对于同代引用来说仅占极少数
根据这个理论,收集器会将Java堆划分不同的区域,再讲回收对象依据年龄分配到不同的区域。因此就有了Minor GC,Major GC ,Full GC
部分收集(Partial GC)指目标不是完整收集整个Java对的垃圾收集,又包含
新生代收集(Minor/Young GC),只是新生代的垃圾收集
老年代收集(Major/Old GC),只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。Major GC有点混淆,要根据上下文来区分到底是老年代收集还是整堆收集。
算法
有了理论,就可以搞算法了,三个基础算法:
标记-清除算法Mark-Sweep
该算法分为'标记','清除',两个阶段:标记所有要回收的对象,标记完成后,统一回收掉所有被标记的对象。标记过程就是对象是否属于垃圾的判断过程。
该算法有2个缺点
- 执行效率不稳定,当堆中包含大量对象,而且其中大部分都是要被回收的,就必须同时大量标记和清除的动作,执行效率会随对象数量增长而降低。
- 空间碎片化的问题,会产生大量不连续的内存碎片,可能会导致无法找到足够的空间而进行另一次GC。
标记-复制算法Mark-Copy
该算法的出现是为了解决标记-清除算法在面对大量可回收对象时执行效率低的问题。
主要是将内存按容量划分为大小相等的两块,每次只使用其中的一块。当块A的内存空间用完了,就将还存活的对象复制到另一块块B上面,然后把已经使用的块A一次清理掉。
优势在于在极少数对象存活的情况下,复制的开销小,而且分配内存的时候不会有碎片问题,简单高效。
这个算法看上去很美,但是也是存在问题的,
- 首先,高效的背后是要付出代价的,就是将可用内存缩小为原来的一半
- 在存活的对象很多的情况下,复制的开销就会上升,效率就会降低,而且还需要有额外的空间做分配担保
HotSpot虚拟机默认Eden和Survivor的大小比例为8:1,即每次新生代中的可用容量是90%,有一个Survivor的空间是空闲下的。
标记-整理算法Mark-Compact
该算法在标记过程中和‘标记-清除’一样,但是第二阶段不是直接对可回收对象进行清理,而是将所有存活的对象都向内存空间的一端移动,然后直接清理边界外的内存。2者差异在于一种是非移动式,而标记整理就是移动式的。
缺点:因为移动对象是一项很重的操作,必须全程暂停所有用户应用才能进行,这样的停顿也被设计者描述为“Stop The World”。
移动会导致回收的时候复杂,不移动会使分配时更复杂。
小结:没有哪一种方法是通用的,每一种算法都有适合自己的场景和区域,所以才会在不同的区域,产生各种收集器。