乐趣区

关于jvm:JVM-GC-垃圾收集算法总结

jvm 的垃圾收集算法总结起来有 4 种,严格来说应该是 3 种,上面一一具体介绍。因为垃圾收集算法波及程序细节,而且各个平台的虚拟机操作内存的办法不同,因而这里不过多介绍算法的实现,重视几种算法的思维及倒退过程。

1. 标记 – 革除算法

最根底的算法“标记 - 革除”(Mark-Sweep)算法,算法分为“标记”和“革除”两个阶段:首先标记出所有要回收的对象,在标记实现后同一回收掉。之所以说它是最根底的收集算法,是因为后续的算法都是基于这种思路并对其有余进行改良失去的。
次要有余:

  1. 一个是效率问题,标记和革除两个效率都不高。
  2. 另一个是空间问题,标记革除后产生大量不间断的内存碎片,空间碎片太多可能会导致当前在程序运行中须要调配较大对象时,无奈找到足够的间断内存而不得不提前触发另一次 GC。

标记 - 革除算法执行过程如图所示。

从图中能够看出,有很多不间断的内存空间碎片。

2. 复制算法

为了解决效率问题,一种称为“复制”的收集算法呈现了,它将可用的内存空间分为大小相等的两块,每次只应用其中一块。当一块内存用完了,就将还存活的对象都复制到另一块上,而后再把本人应用的空间一次清理掉。这样使的每次都对整个半区进行内存回收,内存调配是不必思考空间碎片问题,只有一动堆顶指针,按程序分配内存即可,实现简略,运行高效。
有余:
将内存放大为原来的一半,每次只有一半的内存空间可用,代价高了一点。
复制算法的执行过程如下图:

当初的商业虚拟机都采纳这种算法回收新生代,IBM 公司的专门钻研表明,新生代中的对象 98% 是“朝生夕死”的,所以不须要依照 1: 1 的比例来划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次应用 Eden 和其中的一个 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性地复制到另一块 Survivor 空间上,最初清理掉 Eden 和方才用过的 Survivor 空间。HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8: 1,也就是每次新生代可用内存空间为整个新生代容量的 90%(80%+10%),只有 10% 的内存会被节约。当然,98% 的对象可回收只是个别场景下的数据,咱们没有方法每次回收都只有不大于 10% 的对象存活,当 Survivor 空间不够用时,须要依赖其余内存(这里指老年代)进行调配担保(Handle Promotion)。
调配担保的艰深讲法:比方咱们去银行借款,如果咱们信用很好,在 98% 的状况下能按时偿还,银行可能也会默认咱们能按时还款,只须要有个担保人能保障咱们不能按时还款,能够从他的账户扣钱,那银行就认为没危险了。内存调配担保也一样,如果另一块 Survivor 空间没有足够空间来寄存上次收集后还存活的对象,这些对象将间接通过调配担保机制进入老年代。

3.“标记 - 整顿”算法

复制算法在对象存活率高的状况下要进行较多的复制操作,效率会变低。更要害的是,如果不想节约 50% 的空间,就须要有额定的内存空间调配担保,以应答被应用内存中所有对象都 100% 存活的极其状况,所以老年代个别不间接选用这种算法。
依据老年代的特点,有人提出了另外一种“标记 - 整顿”(Mark-Compact)算法,标记过程与“标记 - 革除”算法一样,但后续步骤不是间接对可回收对象进行清理,而是让所有存活对象都向一端挪动,而后间接清理掉端边界以外的内存。
回收过程如下图所示:

4. 分代收集算法

以后商业虚拟机垃圾收集都采纳分代收集(Generational Collection)算法,这种算法没有什么新的思维,只是依据对象存活周期不同将内存划分为几块。个别是把 Java 堆分为新生代和老年代,这样能够依据各个年代的特点采纳最合适的算法。在新生代,每次垃圾收集时发现都有少量的对象死去,只有大量存活,那就选用复制算法,只须要付出大量的存活对象复制老本即可实现收集。而老年代中对象因为存活率高、没有额定的空间对它调配担保,就必须应用“标记 - 清理”或“标记 - 整顿”算法来进行回收♻️。

最初

贴上一张 JVM 内存分区示意图,加深一下各个分区的印象,有助了解分代收集的思路。

退出移动版