04-jvm-GC算法-算法理论

317 阅读4分钟

垃圾收集算法

标记-清除算法

image

  • 标记清除算法是最基础的收集算法,为了解决引用计数法的问题而提出的,他使用了根集的概念,
  • 分为“标记”和“清除”两个阶段:首先标记出所需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,它的标记过程其实就是前面的跟搜索算法中判断垃圾对象的标记过程
  • 之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的 。
    image
优点:
  • 不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效
缺点
  • 标记和清除的过程效率都不高:这种方法需要使用一个空闲列表来记录所有的空闲区域及大小
  • 标记清除后,会产生大量不连续的内存碎片:虽然空闲区域的大小是足够的,但却可能没有一个单一区域能够满足这次分配所需的大小,因此本次分配还是会失败(在Java中就是一次OutOfMemoryError)不得不触发另一次垃圾收集动作
标记-整理算法 又称之:标记-压缩算法

image

  • 该算法标记过程与标记-清除算法中的标记过程一致,但是对标记过程后的垃圾对象处理情况不同
  • 它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后清除掉端边界以外的内存
  • 在基于标记-整理算法的收集器的实现中,一般增加句柄和句柄表
    image
优点
  • 经过整理后,新对象分配只需要通过指针碰撞便能完成
  • 使用这种方法空闲区域的位置始终是可知的,也不会再由碎片的问题
缺点
  • GC的暂停时间会加长,因为需要将所有的对象都拷贝到一个新的地方去,还得更新他们的引用地址
复制算法

image

  • 该算法的提出是为了解决句柄的开销和堆碎片的垃圾回收
  • 他将内存按照容量分为大小相等的两块,每次只使用其中一块(对象面),当这一块的内存用完了,就将该快还存活的对象复制到另一块的内存上面(空闲面),然后再把已经使用过的内存空间清理掉
  • 复制算法比较适合新生代,在老年代中,对象存活率比较高,如果执行较多的复制操作,效率将会降低,所以老年代一般会选用其他的算法,如:标记-整理算法
  • 现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将新生代划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和使用过的那一块 Survivor。
  • HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间存储放不下的对象

image

优点
  • 标记阶段和复制阶段可以同时进行
  • 每次只对一块内存进行回收,运行高效
  • 只需要一定栈顶指针,按照顺序分配内存即可,实现简单
  • 内存回收的时候,不必考虑内存碎片
缺点
  • 需要一块能容纳下所有存活对象的额外内存空间,因此,可一次性分配的最大内存缩小了一半
分代收集
  • GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。

  • “分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法

  • 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集

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

    如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现