JVM中的垃圾回收算法

我们知道当堆中内存满了的时候,JVM就会使用可达性分析算法,检查对象是否有被 GC Root 引用。如果一个对象没有被任何 GC Root 引用,那就说明它是一个无效的对象,就会被垃圾回收器回收。此外,即使是被一个 GC Root 引用,但如果是弱引用,那这个对象任然有被回收的风险。

现在我们知道了什么情况下一个对象会被回收,那 JVM 是怎么回收一个对象的呢?

前面说了没有被 GC Root 引用的对象将会被回收。现在我们模拟新生代满了,然后标记出没有被 GC Roots 引用的对象,并进行回收:

普通回收

假如说我们将无效的对象直接回收,那么内存中就会出现上图所示这样一个个空缺,这些空缺叫做「内存碎片」。出现过多的内存碎片会产生什么问题么?

可以想象我们总的剩余的内存很多,但是都是一块块不连续的空间。这时候进来一个新的对象,即使总的剩余内存足够多,但是这个对象可能无法找到一块足够大的连续内存。总的来说就是内存碎片越多,内存的利用率就越低

复制算法

复制算法就是为了解决内存碎片问题,它将新生代分成两块区域。使用其中一块内存来存放对象,当它满了之后,任然回收其中无效的对象。不过在回收无效对象之后,它会把一块内存中所有存活的对象,紧挨着拷贝到另一个块内存中。如下图所示:

复制算法

不过我们可以思考一下这样使用复制算法有什么弊端?弊端其实也是很明显的,就是只能使用一半的内存,解决了内存碎片问题,空间利用率还是没有上去。

优化的复制算法

优化的复制算法将新生代按照 8:1:1 分成 Eden区和两个 Survivor 区。当 Eden 区满的时候就会触发 Minor GC,回收 Eden 区及其中一个 Survivor 区中的对象,并将回收之后存活的对象都复制到另一个 Survivor 区。

优化的赋值算法

由于新生代中最终能存活下来的对象在少数,也就是说在一次垃圾回收一直大部分的对象都会消失,所以一个不是很大的 Survivor 区就足够容纳幸存的对象了。Eden 区又比之前普通的复制算法可以利用的区域大不少,大大提高了新生代的内存利用率。

标记清理算法

前面介绍了新生代常用的垃圾回收算法,复制算法。再来看看老年代常用的垃圾回收算法,标记清理算法。

老年代一般选择的垃圾回收器是 CMS,采用标记清理算法。简单来说,标记清理算法分成两步:标记出哪些对象是垃圾对象,再一次性把这些对象清理掉。

举例来说,当程序触发了Full GC 回收老年代的垃圾对象。

  • 先通过追踪 GC Roots 的方法,看老年代中的各个对象是不是被 GC Roots 引用,如果被引用就是存活对象,否则就是垃圾对象。这些垃圾对象就会被标记出来。
  • 然后再一次性清理被标记出来的垃圾对象

但是由于 Full GC 的时间比 Minor GC 的时间差不多长 10 倍,如果和 Minor GC 一样,Full GC 采用先 Stop the World 再用标记清理算法回收垃圾,就会导致系统卡死时间过长,很多响应无法处理。

所以 CMS 让系统一边工作,一边进行垃圾回收。CMS 垃圾回收的过程一共分为 4 个阶段:

  1. 初始标记Stop the World 停止所有工作线程,仅标记 GC Roots 直接引用的对象。这一步速度很快。
  2. 并发标记。此时允许系统线程创建对象,继续运行。垃圾回收线程则会追踪老年代中的对象是否从根源上被 GC Roots 引用。这一步是最耗时的,但是和系统程序并发运行,不会对系统运行造成影响。
  3. 重新标记Stop the World 重新标记在第二阶段里新创建的对象,以及原本被引用现在失去引用的对象。由于只对第二阶段有变动的对象进行标记,速度也比较快。
  4. 并发清理。系统随意运行,同时清理之前被标记为垃圾的对象。这个阶段很耗时,但是由于和系统程序并发运行,并不影响系统程序的运行。
发布了190 篇原创文章 · 获赞 17 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/shuiCSDN/article/details/104001156
今日推荐