JVM垃圾收集算法

Posted 筱白爱学习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM垃圾收集算法相关的知识,希望对你有一定的参考价值。

目录

垃圾收集算法

分代收集理论

三个分代假说

不同分代收集名词

标记-清除算法

算法原理

算法缺点

标记-复制算法

算法原理

算法优点

算法缺点

标记-整理算法

算法优点

算法缺点


垃圾收集算法

垃圾收集算法划分为

  •  “引用计数式垃圾收集”(Reference Counting GC)----直接垃圾收集
  •  “追踪式垃圾收集”(Tracing GC)两大类,----间接垃圾收集

分代收集理论

分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则。

三个分代假说

        1)弱分代假说(Weak Generationnal Hypothesis):绝大多数对象都是朝生夕灭的。

        2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。

共同点:

        收集器应该将Java堆花费出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;

如果剩下的都是难以消亡的对象,那把他们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。

        3)跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。

由来:假如要现在进行一次只局限于新生代区域内的收集(Minor GC),但新生代中的对象是完全有可能被老年代所引用的,为了找出该区域中的存活对象,不得不固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析结果的正确性,反过来也是一样。

遍历整个老年代所有对象的方案虽然理论上可行,但无疑会为内存回收带来很大的性能负担。 所以才产生了第三条经验法则,跨代引用假说。

不同分代收集名词

        部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集,分为以下:

名词解释
新生代收集(Minor GC/Young GC)指目标知识新生代的垃圾收集
老年代收集(Major GC/Old GC)

指目标只是老年代的垃圾收集。

目前只有CMS收集器会有单独收集老年代的行为。

混合收集(Mixed GC)

指目标是收集整个新生代以及部分老年代的垃圾收集。

目前只有G1收集器会有这种行为。

整堆收集(Full GC)收集整个Java堆和方法区的垃圾收集。

标记-清除算法

        最早出现也是最基础的垃圾收集算法是“标记-清除”(Mark-Sweep)算法,在1960年由Lisp之父John McCarthy所提出。

算法原理

  1. 首先标记出所有需要回收的对象,
  2. 在标记完成后,统一回收掉所有被标记的对象,
  3. 也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

算法缺点

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

标记-复制算法

        标记-复制算法简称为复制算法。1969年Fenichel提出了一种称为“半区复制”(Semispace Copying)的垃圾收集算法。

算法原理

  1. 将可用内存按容量划分为大小相等的两块,每次只是用其中的一块。
  2. 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
  3. 如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销,但对于多数对象都是回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。

        

算法优点

  • 解决了“标记-清除算法”的执行效率不稳定问题,该算法主要应用于新生代,新生代对象大部分都是朝生夕灭,新生代被划分为Eden、From Survivor、To Survivor,每次使用Eden和一个Survivor用于新生对象的存储。垃圾回收时就把Eden、From Survivor中存活的对象复制到另一个Survivor上,这样便不会有执行效率不稳定的情况,因为大部分对象都是朝生夕灭的(该优点是相对于新生代来说)。
  • 解决了“标记-清除算法”的空间碎片化问题,因为是清空Eden FromSurvivor所以不会再有不连续的空间碎片。

算法缺点

由于JVM中的绝大多数对象都是瞬时状态的,生命周期非常短暂,所以复制算法被广泛应用于年轻代中。

  • 标记-复制算法因为必须有一部分空间时刻空闲着,所以会有一定的空间浪费
  • 正在极端情况下To Survivor区域不一定能存储的了新生代存活下来的对象,所以需要分配担保策略(对象进入老年代)。

标记-整理算法

        标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都是100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

        1974年Edward Lueders提出了另外一种有针对性的“标记-整理”(Mark-Compact)算法,其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

算法优点

  • 解决了“标记-清除算法”的空间碎片化问题
  • 解决了“标记-复制算法”需要分配担保的问题

算法缺点

        根据强分代假说“熬过越多次垃圾收集的对象,越难以被回收”,老年代中的大部分对象都是年龄达到了16的对象,都是很难被回收的,所以采用“标记-整理算法”去移动对象,对应用程序的吞吐量其实影响很大,但是不得不使用“标记-整理算法”,因为“标记-清除算法”会浪费一定空间,“标记-复制算法”又必须有分配担保策略也需要浪费空间,且“标记-复制算法”也无法满足老年代中所有对象都存活的极端情况。

作者:筱白爱学习!!

欢迎关注转发评论点赞沟通,您的支持是筱白的动力!

jvm 垃圾收集算法

经过上篇如何判断对象是否死亡,那么jvm要对死亡的对象进行垃圾回收,垃圾回收的算法主要有以下几种:

技术图片

一、标记-清楚算法

该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:

  • 一个是效率问题,标记和清除两个过程的效率都不高
  • 一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作

技术图片

黑色代表要回收的对象,灰色代表不用回收的对象,白色代表空闲的内存。

二、复制算法

为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

技术图片

这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

现在的商业虚拟机都采用这种收集算法来回收新生代,研究表明,新生代中的对象 98%是“朝生夕死”的,所以并不需要按照 1:1 的比例来划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。 Survivor from 和Survivor to ,内存比例 8:1:1

当回收时,将 Eden 和 Survivor 中还存活着的对象一次性地复制到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才用过的 Survivor 空间。HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8:1, 也就是每次新生代中可用内存空间为整个新生代容量的 90% (80%+10%),只有 10% 的内存会被“浪费”。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于 10%的对象存活,当 Survivor 空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。

三、标记-整理算法

根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

技术图片

标记整理算法解决了什么问题

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费 50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都 100%存活的极端情况,所以在老年代一般不能直接选用这种算法

四、分代收集算法

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

 

以上是关于JVM垃圾收集算法的主要内容,如果未能解决你的问题,请参考以下文章

jvm,深入理解java虚拟机,垃圾收集算法与垃圾收集器

jvm,深入理解java虚拟机,垃圾收集算法与垃圾收集器

JVM垃圾收集算法

JVM详解——垃圾回收算法

JVM-11. 垃圾回收概述及算法

大话JVM:垃圾收集算法