本文最后更新于:2020年9月7日 上午

JVM系列(五)-垃圾收集算法

今天继续来看一下看垃圾收集的算法,通过垃圾收集的算法,我们可以知道无用的对象是如何被GC收集的。

本篇主要内容有:

  • 1、垃圾收集算法的分类
  • 2、分代收集理论
  • 3、标记-清除算法
  • 4、标记-复制算法
  • 5、标记-整理算法
  • 6、图表总结

1、垃圾收集算法的分类

1)引用计数式垃圾收集(Reference Counting GC)
2)追踪式垃圾收集(Tracing GC)

2、分代收集理论

分代收集理论的本质

本质是一套符合大多数程序运行的经验法则

分代收集理论建立的基础

弱分代假说:绝大多数对象都是朝生夕灭的。
强分代假说:经过多次GC过程的对象,就变得越来越难以消亡。
跨代引用假说:跨代引用相对于同代引用来说仅仅占极少数。

部分垃圾收集器的一致设计原则

将Java堆按照其回收对象年龄(年龄即对象熬过的垃圾回收次数)的分布,分配到不同的区域中存储。

分代收集的好处

1、对于将要回收的对象,放在一起,只需要管理少部分存活的对象,用低代价回收到大量的空间。
2、对于难以回收的对象,同样把他们集中放一起,虚拟机使用较低的频率来回收这个部分。

好处:这样做同时兼顾了垃圾收集的时间开销和内存空间的有效利用。

分代收集的困难

困难:跨代引用。

对象不是独立存在的,对象之间存在跨代引用。不可能为了少部分的跨代引用,而去扫描整个老年代。

所以增添了第三点法则,它的隐含含义是说。存在跨代引用的对象,更倾向于同时生存或者同时死亡。

记忆集

为了解决上面分代收集的困难引入了记忆集这样一个数据结构。

这个结构把老年代划分成为了若干小块,然后标识出哪一个块的内存存在跨代引用,
只有包含了跨代引用的小块内存才会被GC Roots扫描。

3、标记-清除算法

标记-清除算法的过程

阶段: 标记   清除

先标记要清除的对象,然后统一清除掉被标记的对象。(反过来也可以,非移动式的)

标记-清除算法存在问题

1、执行效率不稳定。

    有可能标记对象很多也可能很少。堆中的对象可能大部分需要回收,这样大量的进行标记,标记和清除过程的执行效率随对象数量的增长而降低。

2、内存空间碎片化。

    空间碎片化可能会导致当程序需要分配较大对象时,无法找到足够的连续内存,不得不再一次除法垃圾收集动作。


4、标记-复制算法

标记-复制算法的过程

过程:将内存按照容量划分为大小相等的两块。每次用完一块后,就将还存活的对象的对象复制到另外一块上,然后把已经用过的清除掉。

标记-复制算法的优缺点

优点:实现简单(只需移动栈顶指针,按顺序分配内存即可)、运行高效(每次对一块内存进行回收)、无碎片化问题

缺点: 1、可用内存变为了原来的一半,空间浪费严重 2、如果大量的对象存活,将会有大量的复制开销。

标记-复制算法的实际应用

现在Java虚拟机大多数情况采用的都是标记-复制算法回收新生代。

IBM研究表示新生代中的对象有98%都熬不过第一轮,所以不需要按照1:1的比例来划分新生代的内存空间。

标记-复制算法的优化–Appel式回收

1、应用:

    HotSpot虚拟机的Serial、Parnew等新生代收集器均采用这种策略设计新生代的内存布局。

2、空间划分:

    Appel式回收,将新生分成一块较大的Eden空间和两块较小的Survivor空间(两块空间的目的是为了让空间来回倒腾)。

3、回收过程:

    每次只是用Eden和其中的一块Survivor空间,在垃圾回收过程中,将Eden和Survivor存活的对象一次性复制到另一块Survivor空间上,然后直接清除掉使用过的空间。

4、空间占比:

    HotSpot默认Eden和Survivor的大小比例为8:1

5、逃生门安全设计:

    设计目的:Appel式回收中有一个逃生门的安全设计,因为无法保证每一次都不多于10%的对象存活。

    执行过程:如果Survivor空间无法容纳Minor GC后的存活对象,就需要依赖其他内存区域(大部分为老年代)进行分配担保。(类似于银行的分配担保机制)

5、标记-整理算法

标记-整理算法的过程

过程:

    和标记-清理算法类似,先标记所有需要清除的对象,然后让所有存活的对象都向一端移动,然后清除掉边界以外的内存。(移动式的)

标记-整理算法移动的利弊

我们需要明白的是,是否移动回收后的对象是一项优缺点并存的决策:
- 移动造成内存回收时更复杂。

    移动会更新所有引用是负重的操作,而且这种移动必须全部暂停用户应用才能进行。

- 不移动则内存分配更加复杂。

    不移动会导致碎片化问题,只有依赖更加复杂的内存分配器和内存访问器解决。

如何选择

看关注点是吞吐量(复制器(垃圾收集的用户程序)与收集器的效率总和)还是延迟。

- 如果关注吞吐量,就使用标记-整理算法(Parallel Scavenge收集器)
- 如果关注延迟,就使用标记-清除算法(CMS收集器)

还有一种”和稀泥“两种解决办法都在使用的方式:

    大多数情况采用标记-清除算法,直到碎片化问题影响到了对象分配,再采用标记-整理算法收集一次。(CMS收集器)

6、图表总结

当前商业虚拟机的垃圾收集都采用分代收集的方式,它根据对象的存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代。

Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)时,就会晋升到老年代中。

  • 在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用复制算法来完成收集。

  • 老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!博客中转载文章会注明出处,若有版权问题,请及时与我联系!谢谢!

计算机网络系列(五)-数据交换-电路交换 上一篇
计算机网络系列(四)-Internet结构 下一篇