1 GC 算法简介
算法 | 特点 |
---|---|
标记 - 清除 | 分为 “标记” 和“清除”两个阶段 |
复制 | 可以解决效率问题,将可用的内存按容量划分为大小相等的两块。 |
标记 - 整理 | 先标记、再整理,最后清理 |
分代收集 | 划分新生代和老年代 |
2 标记 - 清除
2.1 流程
分为 "标记" 和 "清除" 两个阶段:
(1) 首先标记出所需要回收的对象 (引用计数法和可达性分析, 两次标记过程);
(2) 在标记完成后统一回收所有被标记的对象.
2.2 缺点
(1) 效率问题: 标记和清除两个过程的效率不高;
(2) 空间问题: 标记清除后会产生大量不连续的内存碎片, 导致以后在程序运行过程中需要分配较大对象时, 无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作.
3 复制
3.1 流程
可以解决效率问题, 将可用的内存按容量划分为大小相等的两块.
(1) 每次只使用其中的一块;
(2) 当这一块用完了, 就将还存活的对象复制到另一块上;
(3) 然后再把已使用的内存空间清理掉.
3.2 优点
每次对整个半区进行内存回收, 避免内存碎片问题, 只需移动堆顶指针, 按顺序分配内存即可, 实现简单, 运行高效.
3.3 缺点
将内存缩小为原来的一半, 代价高; 当对象存活率较高时需要进行较多的复制操作, 效率降低.
3.4 应用
回收新生代, 新生代中分为 Eden 空间和两块较小的 Survivor 空间, 每次使用 Eden 和其中的一块 Survivor. 默认 Eden:Survivor=8:1,Survivor 不够时, 老年代内存分配担保.
4 标记 - 整理
4.1 流程
(1) 首先标记处所需要回收的对象;
(2) 不直接对可回收对象进行清理, 让所有存活的对象都向一端移动;
(3) 直接清理掉端边界以外的内存.
4.2 优点
改进了复制算法在对象存活率较高时带来的效率问题;
4.3 应用
老年代收集 (对象存活率较高)
5 分代收集
5.1 思想
根据对象存活周期的不同将内存划分为新生代和老年代, 根据各自的特点采用合适的收集算法.
(1) 新生代中, 每次垃圾收集时都发现有大批对象死去, 少量存活, 选用复制算法;
(2) 老年代中, 对象存活率高, 没有额外空间进行分配担保, 使用 "标记 - 清理" 或者 "标记 - 整理".
6 QA
6.1 为什么不是 1 块 Survivor 空间而是 2 块?
这里涉及到一个新生代和老年代的存活周期的问题, 比如一个对象在新生代经历 15 次 GC 回收, 就可以移到老年代了. 问题来了, 当我们第一次 GC 的时候, 我们可以把 Eden 区的存活对象放到 Survivor-1 空间, 但是第二次 GC 的时候, Survivor-1 空间和 Eden 区的存活对象也需要再次用复制算法, 放到 Survivor-2 空间上, 而把刚刚的 Survivor-1 空间和 Eden 空间清除. 第三次 GC 时, 又把 Survivor-2 空间和 Eden 区的存活对象复制到 Survivor-1 空间, 如此反复.
所以, 这里就需要 2 块 Survivor 空间来回倒腾.
6.2 为什么 Eden 空间这么大而 Survivor 空间要分的少一点?
新创建的对象都是放在 Eden 空间, 这是很频繁的, 尤其是大量的局部变量产生的临时对象, 这些对象绝大部分都应该马上被回收, 能存活下来被转移到 survivor 空间一般不是很多. 所以, 设置较大的 Eden 空间和较小的 Survivor 空间是合理的, 大大提高了内存的使用率, 缓解了复制算法的缺点.
8:1:1 这种比例就挺好的, 当然这个比例是可以调整的, 包括上面的新生代和老年代的 1:2 的比例也是可以调整的.
新的问题又来了, 从 Eden 空间往 Survivor 空间转移的时候, 如果出现 Survivor 空间不够了怎么办? 直接放到老年代去. 有的对象来回在 Survivor-1 区或者 Survivor-2 区呆了比如 15 次, 就被分配到老年代 Old 区; 有的对象太大, 超过了 Eden 区, 直接被分配在 Old 区; 有的存活对象, 放不下 Survivor 区, 也被分配到 Old 区. 如果老年代 Old 区也被放满了, 就是一次大 GC 即为 Major GC.
来源: https://www.cnblogs.com/Andya/p/12435016.html