只要面试都会问到的垃圾收集算法,还不赶快收藏!!!

垃圾收集算法

1. 分代收集理论

1.1 分代假说

  1. 弱分代假说:绝大多数的对象存活时间很短,朝生夕灭。
  2. 强分代假说:熬过越多次的垃圾回收次数,对象越难被消灭。
  3. 跨代引用假说:跨代引用相对于同代引用而言仅仅只占一小部分。

1.2 垃圾回收器设计

基于弱分代假说和强分代假说,多款常用垃圾回收器的统一设计原则:收集器应该将Java堆划分不同的区域,根据对象年龄分配待不同的区域中存。

在Java堆内存中,分两部分:

  • 新生代:这部分区域中的对象存活时间都很短,基本遇到垃圾回收就会被清除
  • 老年代:这部分区域中的对象存活时间很久了,熬过多次垃圾回收 ,年龄很大,很难被清除。

对于新生代的垃圾回收,垃圾收集器并不需要去关注回收的对象,只需要关注存活下来的对象。 每次回收后存活的少量对象,将逐步升级到老年代当中。

对于老年代的垃圾回收,虚拟机可以用较低的频率回收这个区域。这样子就同时兼顾了垃圾回收的时间开销和内存空间的有效利用。

将Java堆分成新生代和老年代分别使用不同的策略垃圾回收之后,出现一个问题:跨代引用。存在相互引用的两个对象,应该是同时生存同时消亡的。如果新生代与老年代的对象存在跨代引用,那么由于老年代的关系所以这两个对象很难被回收。当新生代对象到达足够年龄之后,将进入老年代,此时的跨代引用就消除了。

很好的办法,等就对了。当然不是!那样子老年代就会越来越臃。但是我们又不想为了一个跨代引用对整个老年代进行扫描,那么就通过一个叫记忆集的结构来解决。

只要面试都会问到的垃圾收集算法,还不赶快收藏!!!

记忆集将老年代划分为若干个小块,标记了老年代的哪一块存在跨代引用。当新生代进行垃圾回收的时候,通过记忆集将包含了跨代引用的那一小部分老年代也会进行垃圾回收。

1.3垃圾回收

  • 新生代收集 Minor GC / Young GC:指目标指示新生代的垃圾收集
  • 老年代收集 Major GC / Old GC:指目标指示老年代的垃圾收集
  • 混合收集 Mixed GC:整个新生代和部分老年代的垃圾收集
  • 整堆收集 Full GC:收集整个 Java 堆和方法区的垃圾收集

2. 标记-清除算法

2.1 介绍

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回 收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回 收所有未被标记的对象。

只要面试都会问到的垃圾收集算法,还不赶快收藏!!!

2.2 缺点

  1. 执行效率不稳定,效率随着对象数量的增加而降低
  2. 内存碎片问题,清除之后出现不连续的内存碎片,当需要为大对象分配足够的连续内存的时候,需要再次GC

3. 标记-复制算法

3.1. 介绍

它将可用 内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着 的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

只要面试都会问到的垃圾收集算法,还不赶快收藏!!!

3.2 缺点

  1. 如果存活对象过多,可能会有大量的时间浪费复制上,所以这方法主要是针对存活率小的情况。
  2. 空间浪费,,一般的空间作为保留区域。

3.3 作用

这种收集算法多用于回收新生代。

因为新生代中有98%的对象都熬不过第一轮的回收,只有2%的对象可以存活下来。如果盲目将新生代划分为1 : 1的比例,就会浪费很多的空间。所以厂商们将新生代的布局划分为了一块较大的 Eden 空间和两块较小的 Survivor 空间。每次分配内存都是只是用 Eden 和其中一块 Survivor 空间。当 Minor 垃圾回收后,将存活的对象存放在保留的 Survivor 空间中,清空 Eden 和之前使用的 Survivor 空间。

HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8 : 1,也就是每次新生代中可用的空间为 90%,只有一个 Survivor 会被浪费。当遇到一个 Survivor 空间不足以容纳一次 Minor GC 后存活的对象时,就需要依赖老年代进行分配担保。新生代的分配担保就是指空间不够放下存活对象,就会将这些对象通过分配担保机制直接进入老年代。

4. 标记-整理算法

4.1 介绍

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存

只要面试都会问到的垃圾收集算法,还不赶快收藏!!!

5. 总结

在 GC 过程中移动存活对象,并更新所有引用这些对象的地方的数据,是一种极为负重的操作,而且移动操作必须暂停用户应用进程才能进行。这种暂停被描述为“ Stop The World”,也就是 STW。

如果像标记-清除算法那样子完全不考虑移动和整理,Java 堆中的空间碎片问题将十分严重,只能依赖更复杂的内存分配器和内存访问器来解决。内存访问是用户程序中最频繁的操作,如果在此环节上添加额外的负担,势必会直接影响程序的吞吐量。

那么移动会发生 STW,但是内存分配的时候更简单。直接清除会产生内存碎片,但是垃圾回收的时候更方便。无论是移动与否都有弊端。从垃圾回收的 STW 来看,直接清除的 STW 最短,甚至不用停顿,但从整个程序的吞吐量来看,移动对象更划算。不同的虚拟机实现厂商的注重点不同,他们的收集器也不一样。

还有一种“和稀泥式”解决方案可以不在内存分配和访问上增加太大额外负担,做法是让虚拟机平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间。前面提到的基于标记-清除算法的 CMS 收集器面临空间碎片过多时采用的就是这种处理办法。

© 版权声明

相关文章