JVM垃圾回收几种常见算法和常见收集器

Posted java技术阅读

tags:

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

上文 我们讲解了需要考虑垃圾回收的内存结构、类型,垃圾回收基础算法和垃圾回收的时机。 接下来我们重点研究下几种常见的垃圾回收算法和垃圾收集器。


I 垃圾回收算法
几种常见的垃圾回收算法:标记——清除算法、复制算法、标记——整理算法和分代回收算法。

标记——清除算法

标记——清除算法 的基本思想跟它的名字一样,分为“标记”和“清除”两个阶段;首先标记(判断对象是否回收需要两次标记,遵循上文提到的原则)出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

标记——清除算法是几种GC算法中最基础的算法,标记和清除效率不高,产生大量不连续的内存碎片,导致创建大对象时找不到连续的空间。后续的收集算法都是基于这种思想并对其不足进行改进而实现的。


复制算法

将可用内存按容量分为大小相等的两块,每次只使用其中一块,当这一块的内存用完,就将还存活的对象复制到另外一块内存上,然后再把已使用过的内存空间一次清理掉。

JVM垃圾回收几种常见算法和常见收集器

复制算法 实现简单,效率高,解决了标记——清除算法内存碎片问题。但是代价太大,内存缩小了一半,效率随对象的存活率升高而降低。

现在的商业虚拟机都采用这种收集算法来回收新生代,但并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。


标记——整理算法

标记——整理算法 标记过程和标记——清理算法一致,也是基于可达性分析算法。和标记——清理算法不同的是,该算法不是针对可回收对象进行清理,而是根据存活对象进行整理。让存活对象都向一端移动,然后直接清理掉边界以外的内存。

JVM垃圾回收几种常见算法和常见收集器

标记——整理算法不会像复制算法那样随着存活对象的升高而降低效率,不像标记-清除算法那样产生不连续的内存碎片。但是,除了像标记-清除算法的标记过程外,还多了一步整理过程,效率更低。


分代收集算法

分代回收算法 实际上是把复制算法和标记整理法的结合,并不是真正一个新的算法,一般分为:老年代(Old Generation)和新生代(Young Generation),老年代就是很少垃圾需要进行回收的,新生代就是有很多的内存空间需要回收,所以不同代就采用不同的回收算法,以此来达到高效的回收算法。

新生代:由于新生代产生很多临时对象,大量对象需要进行回收,所以采用复制算法是最高效的。

老年代 :回收的对象很少,都是经过几次标记后都不是可回收的状态转移到老年代的,所以仅有少量对象需要回收,故采用标记清除或者标记整理算法。

I 垃圾收集器

在HotSpot的JVM虚拟机中,有7种垃圾回收器。这其中垃圾回收器也是从一代一代改进来的。当然垃圾回收处理的STW的停顿时间也越来越短。效率越来越高。因为我们的堆中,分了年轻代和老年代。每一个部分中用的内存收集算法是不一样的。针对不同的回收算法就设置了不同的垃圾收集器。

JVM垃圾回收几种常见算法和常见收集器

图中展示了7种作用于不同分代的收集器:serial收集器、parNew收集器、parallel scavenge收集器、serial old 收集器、parallel old收集器、cms收集器、g1收集器。如果两个收集器之间存在连线,则说明它们可以搭配使用。


在介绍7个收集器之前,我们先讲讲几个相关概念
并行收集 :指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
并发收集 :指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。

吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%。


串行收集器(Serial/Serial old)

串行收集器,顾名思义就是串行的,独占式的垃圾回收器。在新生代使用Serial收集器,在老年代使用Serial old收集器。

JVM垃圾回收几种常见算法和常见收集器

Serial:使用在新生代中,采用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销;

Serial old:使用在老年代中,采用标记——整理算法,由于Serial old通常使用会比Serial垃圾回收更长,在堆空间比较大的运用程序中,一旦Serial old启动,应用程序很可能会因此停顿几秒甚至更长时间。

参数配置:

-XX:+UseSerialGC 表示新生代、老年代都使用串行回收器(上图所示)。


新生代并行收集器(parNew)

ParNew 工作在新生代的垃圾收集器,它只是简单地将串行回收器多线程化。它的回收策略、算法以及参数和串行回收器一样。

ParNew也是独占式的回收器,在收集过程中,应用程序会全部暂停。但由于ParNew使用多线程进行垃圾回收,因此,在并发能力比较强的 CPU 上,它产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中,ParNew的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。

配置参数:

  • -XX:+UseParNewGC 表示新生代使用并行收集器,老年代使用串行收集器(上图所示)。

  • -XX:ParallelGCThreads 指定并行收集器工作时的线程数。一般,最好与 CPU 数量相当,避免过多的线程数影响垃圾收集性能。在默认情况下,当 CPU 数量小于 8 个,ParallelGCThreads 的值等于 CPU 数量,大于 8 个,ParallelGCThreads 的值等于 3+[5*CPU_Count]/8]。


并行回收收集器(parallel scavenge/parallel old)

该类收集器与吞吐量关系密切,目标是达到一个可控制的吞吐量,是一个并行的多线程收集器(与ParNew收集器类似))。 该收集器与ParNew最重要的一个区别是GC自适应调节策略。

Parallel Scavenge属于新生代收集器采用复制算法的收集器;Parallel old属于老年代收集器采用标记——整理算法的收集器。


GC自适应调节策略 收集器可设置-XX:+UseAdptiveSizePolicy参数。 当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,jvm会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。

配置参数:

  • -XX:+MaxGCPauseMills 设置最大垃圾收集停顿时间,它的值是一个大于 0 的整数。收集器在工作时会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。如果希望减少停顿时间,而把这个值设置得很小,为了达到预期的停顿时间,JVM 可能会使用一个较小的堆 (一个小堆比一个大堆回收快),而这将导致垃圾回收变得很频繁,从而增加了垃圾回收总时间,降低了吞吐量。

  • -XX:+GCTimeRatio 设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。比如 GCTimeRatio 等于 19,则系统用于垃圾收集的时间不超过 1/(1+19)=5%。默认情况下,它的取值是 99,即不超过 1%的时间用于垃圾收集。

  • -XX:+UseParallelOldGC 在新生代和老生代都使用并行回收收集器,对吞吐量敏感的系统中,可以考虑使用(上图所示)。

  • -XX:ParallelGCThreads 也可以用于设置垃圾回收时的线程数量。

垃圾回收篇幅过长,下文继续讲解最后的内容cms收集器和g1收集器。

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

6.GC垃圾回收算法和垃圾收集器的关系

JVM-垃圾收集算法

JVM学习记录-垃圾回收算法

JVM常见垃圾回收算法

JVM的垃圾算法有哪几种

JVM优化 垃圾回收 算法 垃圾收集器 GC日志可视化查看