深入理解JAVA虚拟机系列之垃圾收集算法

一、简介

  • 本文主要介绍分代收集理论和常见的垃圾收集算法:标记清除、标记复制、标记整理等;

二、分代收集理论

2.1 理论假说

2.1.1 三个假说

  • 弱分代假说:绝大多数对象都是朝生夕灭的;
  • 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡;
  • 跨代引用假说: 跨代引用相对于同代引用来说仅占极少数;

2.1.2 假说推论

  • 强弱分代假说奠定了多款常用的垃圾收集器的一致设计原则:收集器应该将java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储;
  • 存在相互引用的俩个对象是应该倾向于同时生存或者同时消亡的;
  • 依据第三条假说,我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称为“记忆集”,Remembered Set),这个结构把老年代划分为若干个小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生新生代收集时,只有包含了跨代引用的小块内存里面的对象才会被加入到GC Root进行扫描。虽然这种方法需要在对象改变引用关系(如将自己或者某个属性赋值)时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说时划算的;

2.2 垃圾收集分类

2.2.1 从如何判定对象消亡的角度出发

  • 垃圾收集算法可以划分为“引用计数式垃圾收集”(Reference Counting GC)和“追踪式垃圾收集”(Tracing GC)俩大类,这两类也常被称作“直接垃圾收集”和“间接垃圾收集”;
  • 后面所说的各种算法均属于追踪式垃圾收集的范畴;

2.2.2 按照回收区域划分

  • 整堆收集(Full GC): 收集整个java堆和方法区的收集收集;
  • 部分收集(Partial GC):指目标不是完整收集整个java堆的垃圾收集,包含新生代收集、老年代收集和混合收集;
  • 新生代收集(Minor GC/Young GC): 指目标只是新生代的垃圾收集;
  • 老年代收集(Major GC/Old GC): 指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为;
  • 混合收集(Mixed GC): 指目标是收集整个新生代以及部分老年代的垃圾收集,目前只有G1收集器会有这种行为;

三、标记清除算法[Mark-Sweep]

3.1 原理

  • 如名字一样,算法分为标记和清除俩个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象;

3.2 缺点

  • 执行效率不稳定,如果java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除俩个过程的执行效率都随对象数量增长而降低;
  • 标记和清除过后会产生大量不连续的内存碎片,空间碎片太多可能导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作;

四、标记复制算法

4.1 原理

  • 为了解决标记-清除算法面对大量可回收对象时执行效率低的问题而提出的半区复制算法,它将内存按容量分成大小相等的俩块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉;
  • 如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销,但对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可;
  • 针对具备“朝生夕灭”特点的对象,提出了以各种更优化的半区复制分代测录,现在称为“Appel式回收”:把新生代 分为一块较大的Eden空间和俩块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor靠你关键。当Survivor空间不足以容纳一次Minor GC 之后存活的对象时,就需要依赖其他内存区域(实际上大多数时老年代)进行分配担保(Handle Promotion),这些对象边疆通过分配担保机制直接进入老年代;
  • HotSpot虚拟机的Serial、ParNew等新生代收集器均采用了这种策略来设计新生代的内存布局;
  • HotSpot虚拟机默认Eden和Survivor的大小比例时8:1,也即每次新生代中可用内存空间未整个新生代容量的90%,只有一个Survivor空间即10%的新生代是被浪费的;

4.2 缺点

  • 可用内存缩小了一半,空间浪费有点多,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以老年代一般不直接选用该算法;
  • 标记复制算法在对象存活率较高时就需要进行多次复制操作,效率会降低;

五、标记整理算法

5.1 原理

  • 针对老年代对象的存亡特征而提出的另外一种有针对性的算法:其中标记过程与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
  • 标记清除算法与标记整理算法的本质差异是是否移动存活对象,二者优缺点并存;

5.2 缺点

  • 如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行(最新的ZGC和Shenandoah收集器使用读屏障技术实现了整理过程与用户线程的并发执行);

六、总结

posted @ 2021-01-23 23:09  请叫我猿叔叔  阅读(80)  评论(0)    收藏  举报