目录
- 类型
- 标记-清除 算法
- 复制算法
- 标记 - 整理 算法
- 分代收集算法
- 总结
一、类型
垃圾收集算法分为4类:
- 标记-清除 算法
- 复制算法
- 标记 - 整理 算法
- 分代收集算法
接下来分别介绍。
二、标记-清除 算法
这是 垃圾收集算法中 最最基础的算法。
1、算法思想
算法分为两个阶段:
- 标记阶段:标记出所有需要回收的对象;
- 清除阶段:统一清除(回收)所有被标记的对象。
标记阶段请参考(GC)垃圾标记算法详解攻略中的可达性分析法。
清除阶段就是统一清除(回收)所有被标记的对象。
2、优点
算法简单、实现简单
3、缺点
- 效率问题:即 标记和清除 两个过程效率不高
- 空间问题:标记 - 清除后,会产生大量不连续的内存碎片。
这导致 以后程序 需要分配较大空间对象时 无法找到足够大的连续内存 而被迫 触发另外一次垃圾收集行为,这导致非常浪费资源。
下面继续介绍的算法就是为了解决上述两个问题的。
4、应用场景
对象存活率较低 & 垃圾回收行为频率低 的场景
如老年代区域,因为老年代区域回收频次少、回收数量少,所以对于效率问题 & 空间问题不会很明显。
三、复制算法
该算法的出现是为了解决 标记-清除算法中 效率 & 空间问题的。
1、算法思想
- 将内存分为大小相等的两块,每次使用其中一块;
- 当使用的这块内存用完,就将这块内存上还存活的对象 复制到另一块还没试用过的内存上
- 最终将使用的那块内存一次清理掉。
示意图如下:
2、优点
- 解决了标记-清除算法中 清除效率低的问题
每次仅回收内存的一半区域
- 解决了标记-清除算法中 空间产生不连续内存碎片的问题
将已使用内存上的存活对象 移动到栈顶的指针,按顺序分配内存即可。
3、缺点
- 每次使用的内存缩小为原来的一半。
- 当对象存活率较高的情况下需要做很多复制操作,即效率会变低
4、应用场景
对象存活率较低 & 需要频繁进行垃圾回收 的区域
如新生代区域
5、特别注意
a. 背景
新生代区域在进行垃圾回收时,98%对象都必须得回收
b. 问题
复制算法中 每次使用的内存缩小为原来的一半 利用率低 & 代价太高
c. 解决方案
- 不 按 1:1的比例 划分内存,而是 按8:1:1比例 将内存划分为一块较大的 Eden 和两块较小的
Survivor
区域(From Survivor、To Survivor
)
- 每次使用Eden、From Survivor区域;
- 用完后就 将上述两块区域存活的对象 复制到To Survivor区域上
- 最终一次清理掉Eden、From Survivor区域
使用逻辑 同 改进前
很多同学会问,假如 Eden、From Survivor
区域上存活对象所需内存大小 > To Survivor
区域怎么办?
解决方案:依赖老年代内存区域 做 内存分配担保。
即To Survivor区域 存不下来的对象 会通过 内存分配担保机制 暂时保存在老年代
四、标记 - 整理 算法
此算法类似于第一种标记 - 清除 算法,只是在中间加多了一步:整理内存。
1、算法思路
算法分为三个阶段:
- 标记阶段:标记出所有需要回收的对象;
- 整理阶段:让所有存活的对象都向一端移动
- 清除阶段:统一清除(回收)端以外的对象。
示意图如下:
2、优点
- 解决了标记-清除算法中 清除效率低的问题:一次清楚端外区域
- 解决了标记-清除算法中 空间产生不连续内存碎片的问题:将已使用内存上的存活对象 移动到栈顶的指针,按顺序分配内存即可。
3、应用场景
对象存活率较低 & 垃圾回收行为频率低 的场景
如老年代区域,因为老年代区域回收频次少、回收数量少,所以对于效率问题 & 空间问题不会很明显。
五、分代收集算法
主流的虚拟机基本都采用该算法,下面会着重讲解。
1、算法思路
- 根据 对象存活周期的不同 将 Java堆内存 分为:新生代 & 老年代 。分配比例如下:
- 根据 两块区域特点 选择 对应的垃圾收集算法(即上面介绍的算法),具体细节请看下图:
2、具体存储过程
- 新建的对象 一般会被优先分配到新生代的
Eden
区、From Survivor
区
大对象(如很长的字符串以及数组)会直接分配到老年代,这是为了避免在 Eden 区 和 Survivor区之间发生大量的内存复制(因为新生代会采用复制算法进行垃圾收集)
- 这些对象经过第一次
Minor GC
后,若仍然存活,将会被移到To Survivor
区
一次清理掉
Eden、From Survivor
区域
- 在
To Survivor
区每经过一轮Minor GC
,该对象的年龄就+1 - 当对象年龄达到一定时(阈值默认=15),就会被移动到老年代。
- 即新生代的对象在存活一定时间后,会被移动存储到老年代区域。
- 还有一种 新生代对象被移懂到老年代区域的情况是:动态对象年龄判定。即如果在
Survivor
区中 所有相同年龄对象的大小总和 大于Survivor
区内存大小一半时,所有大于或等于该年龄的对象都会直接进入老年代。
特别注意
From Survivor 和 To Survivor
之间会经常互换角色。
每次发生GC时,把
Eden区和 From Survivor
区中 存活且没超过年龄阈值的对象 复制到To Survivor
区中(此时To
Survivor变成了From Survivor
),然后From Survivor
清空(此时From Survivor变成了To Survivor
)
3、优点
效率高、空间利用率高
根据不同区域特点 选择 不同的垃圾收集算法
4、应用场景
现在主流的虚拟机基本都采用 分代收集算法 ,即根据不同区域特点选择不同垃圾收集算法。
- 新生代 区域:采用 复制算法
- 老年代 区域:采用 标记-清除 算法、标记 - 整理 算法
六、总结
用一张图总结上述4个垃圾收集算法:
参考Android进阶解密