写在前面
本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!
本专栏目录结构和文献引用请见100个问题搞定Java虚拟机
解答
垃圾收集算法有标记清除算法,标记整理算法,复制算法,分代收集算法等。
这些算法是基于清除、压缩和复制这3大垃圾收集思路得到的。
补充
3种垃圾收集的思路
当标记完所有的存活对象时,我们便可以进行死亡对象的回收工作了。
主流的基础回收思路可分为三种。
清除(sweep)
即把死亡对象所占据的内存标记为空闲内存,并记录在一个空闲列表(free list)之中。
当需要新建对象时,内存管理模块便会从该空闲列表中寻找空闲内存,并划分给新建的对象。
缺点
-
会造成内存碎片。由于 Java 虚拟机的堆中对象必须是连续分布的,因此可能出现总空闲内存足够,但是无法分配的极端情况。
-
分配效率较低。 如果是一块连续的内存空间,那么我们可以通过指针加法(pointer bumping)来做分配。 而对于空闲列表,Java 虚拟机则需要逐个访问列表中的项,来查找能够放入新建对象的空闲内存。
压缩(compact)
即把存活的对象聚集到内存区域的起始位置,从而留下一段连续的内存空间。
这种做法能够解决内存碎片化的问题,但代价是压缩算法的性能开销。
复制(copy)
即把内存区域分为两等分,分别用两个指针 from 和 to 来维护,并且只是用 from 指针指向的内存区域来分配内存。
当发生垃圾收集时,便把存活的对象复制到 to 指针指向的内存区域中,并且交换 from 指针和 to 指针的内容。
复制这种回收方式同样能够解决内存碎片化的问题,但是它的缺点也极其明显,即堆空间的使用效率极其低下。
现代的垃圾收集器往往会综合上述几种回收方式,综合它们优点的同时规避它们的缺点。
垃圾收集算法
标记清除算法(Mark-Sweep)
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
它可以说是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到的。
标记整理算法(或者叫标记压缩算法)(Mark-Compact)
标记过程仍然与“标记一清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
复制算法(Copy)
同上面的复制思路一样。
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
分代收集算法(Generational Collection)
根据对象存活周期的不同将内存划分为几块。
一般是把Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,Java 对象都是朝生暮死的,选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记一清理”或者“标记一整理”算法来进行回收
垃圾收集算法的优缺点
算法 | 优点 | 缺点 |
---|---|---|
复制 | 吞吐量大(一次能收集整个空间),分配效率高(对象可以连续分配),没有内存碎片 | 堆的使用效率低(需要额外的一个空间 To Space),需要移动对象。 |
标记清除 | 无须移动对象,算法简单 | 内存碎片化,分配慢(需要找到一个合适的空间) |
标记整理 | 堆的使用效率高,无内存碎片 | 暂停时间更长,对缓存不友好(对象移动后顺序关系不存在) |
分代 | 组合算法,分配效率高,堆的使用效率高 | 算法复杂 |