我们来聊聊垃圾收集器中相关术语与新生代收集器

Posted 小孟的coding之旅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我们来聊聊垃圾收集器中相关术语与新生代收集器相关的知识,希望对你有一定的参考价值。

垃圾收集算法为实现垃圾收集提供了强有力的理论支持,而垃圾收集器则是利用了垃圾收集算法去实现垃圾回收的实践落地。

那么和垃圾回收算法类似,Java 也提供了多款垃圾收集器,不同的垃圾收集器也有不同的特性以及适用场景,甚至不同的垃圾收集器之间还可能存在配合使用的关系,那么使用起来还是比较复杂的。下图展示了 Java 里面比较主流的垃圾收集器以及能够作用的内存区域。

其中 Serial、ParNe、Parallel Scavenge 是作用于新生代的,而 CMS、Serial Old 、Parallel Old 作用于老年代。G1 既可以作用于新生代,也可以作用于老年代。连线表示垃圾收集器之间可以配合使用。

理解这张图,这样在你配置垃圾收集器的时候,一来可以避免配置了不能配合的垃圾收集器,比如你配置 CMS 和 Parallel Scavenge 是不能配合的。二来你也能够知道各种垃圾收集器所能够作用的内存区域。

好,接下来在正式探讨垃圾收集器这个话题之前,我们先来普及几个术语。

Stop The World

Stop The World 简写为 STW,也叫全局停顿。在 Stop The World 这个状态下,所有的 Java代码会停止运行,native 代码可以继续运行,但是它没有办法和 JVM 进行交互。

那么 Stop The World 多半是由于垃圾回收导致的,另外也可能是由 Dump 线程死锁检查、Dump 堆等等导致的。

我们试想,为什么在垃圾收集的时候会有 Stop The World 呢?可以这样想,我们的应用在运行的时候会产生大量的对象。那么可以做这样的对比,我们假设应用程序里面所产生的对象好比我们的聚会,那么聚会的时候会产生垃圾,然后垃圾收集器是清洁工负责打扫垃圾。如果你的聚会不停止,不断地产生垃圾的话,那么清洁工永远都别想打扫干净这个场地,只有让大家都停止活动,才可能把屋子打扫干净。所以垃圾收集的时候会有 Stop The World。

那么 Stop The World 有什么危害呢?非常好理解,首先 Stop The World 的状态下,Java 代码都会停止运行。于是你的服务会停掉,你的请求不会有任何响应。直到应用从 Stop The World 状态出来之后,才会恢复响应。那么对于一些高可用的系统,比如有的应用它有主从模式,如果 Stop The World 持续的时间过长,甚至可能会引起主备切换。总而言之, Stop The World 对于生产环境的危害还是比较严重的,因此我们一般都需要尽量缩短 Stop The World 的时间。

并行收集 VS 并发收集

很多人搞不清楚并行和并发之间的区别,我们来看一下什么叫并行收集呢?

它指的是多个垃圾收集线程并行的工作,但是在垃圾收集线程工作的过程中,你的用户线程是处于等待状态的,也就是说在垃圾收集的阶段,有多个线程在回收垃圾,但是与此同时你的业务线程都在等待中。

那么什么是并发收集呢?它指的是你的用户线程和垃圾收集线程同时工作,这个叫并发收集。

吞吐量

吞吐量指的是 CPU 用于运行用户代码的时间和 CPU 总消耗时间的比值。

计算公式:吞吐量=运行用户代码的时间 /(运行代码的时间+垃圾收集时间)。

举个例子,有一个 JVM 总共运行了 100 分钟,垃圾收集花费了 1 分钟,那么吞吐量就是 99% 。

好,了解这几个术语之后,正式探讨 Java 里面的垃圾收集器。

Serial 收集器

先来探讨新生代里面收集器。

第一款垃圾收集器叫 Serial 收集器,也叫串行收集器。那么Serial 收集器是最基本的收集器,也是发展历史最悠久的收集器。它使用的是复制算法。执行过程大概如下图所示:

用户线程运行到一个安全点之后全部暂停,然后由一个垃圾收集线程去回收,垃圾回收完成之后,用户线程才能继续执行。

那么这个垃圾收集器有哪些特点呢?

首先这个垃圾收集器是单线程的。

第二,简单高效。简单很好理解,单线程的当然比较简单了。那么高效是什么意思呢?这里所谓的高效是和其他收集器单个线程的工作效率下相比的,由于 Serial 收集器是单线程的,所以它没有和其他线程交互的开销,专心去做垃圾收集,因此它相对其他的垃圾收集器,单线程的工作效率会相对高一些。

第三,用这个垃圾收集器回收垃圾的时候,工作线程全程暂停,直到收集结束,也就是说整个垃圾收集线程一直处于 Stop The World 的状态。

Serial 收集器适用于什么样的场景呢?

第一,一般来说,它比较适合用来运行一些客户端程序,那么事实上,当你的项目使用 Client 模式运行的时候,默认使用的就是 Serial 收集器。比如你有一个应用在启动的时候,java -client -java xxx,这样使用的就是 Serial 收集器

第二,它比较适合运行在单核机器上,比如说一些嵌入式低性能的机器上。

ParNew 收集器

ParNew 收集器可以认为是 Serial 收集器的多线程版本。因为这个收集器除使用多线程以外,其他特性和 Serial 收集器都是一样的。比如针对 ParNew 收集集的一些 JVM 参数、Stop The World 的表现以及垃圾数据算法都和 Serial 是一致的。ParNew 收集器集的执行过程大致如下图所示:

用户线程执行到安全点之后暂停,然后会有多个垃圾数据线程去回收,垃圾回收完之后,用户线程继续执行。

ParNew 收集器的特点是多线程。另外它可以使用这个参数: -XX:ParallelGCThreads 去设置垃圾收集的线程数,在不同运行环境下,根据 CPU 的核数,开启不同的线程数,从而达到最优的垃圾收集效果。

在多 CPU 时,比 Serial 收集效率高。同时收集过程暂停所有应用程序线程,单 CPU 时比 Serial 效率差。

ParNew 收集器主要用来和 CMS 配合使用。同时运行在Server模式下的虚拟机中首选的新生代收集器。

Parallel Scavenge 收集器

和 ParNew 收集器一样,Parallel Scavenge 收集器也是运行在新生代区域,属于多线程的收集器,但不同的是,ParNew 收集器是通过控制垃圾回收的线程数来进行参数调整,而 Parallel Scavenge 收集器更关心的是程序运行的吞吐量,所以也被称为是吞吐量优先收集器,即一段时间内,用户代码运行时间占总运行时间的百分比。并且采用的算法是复制算法,执行过程和ParNew 也是类似的。

Parallel Scavenge 收集器特点有哪些呢?

首先使用 Parallel Scavenge 收集器可以达到一个可控的吞吐量,Parallel Scavenge 收集器提供了两个参数去控制吞吐量。

第一,-XX:MaxGCPauseMillis:最大垃圾收集停顿时间,是一个大于 0 的毫秒数,比如你可以配置这个时间等于 100 毫秒,收集器将回收时间尽量控制在这个设定值之内,但是也不保证绝对不超过这个值。同时需要注意的是在同样的情况下,回收时间与回收次数是成反比的,回收时间越小,相应的回收次数就会增多,所以这个值并不是越小越好。

第二,-XX:GCTimeRatio:它用来设置吞吐量的大小,取值是(0, 100)之间的整数,表示垃圾收集时间占总时间的比率。假设我们把 GCTimeRatio 的值设成 n,那么系统花费在垃圾收集的时间不会超过 1/(1+n)。

那么 Parallel Scavenge 除提供了两个参数去控制存储量以外,还提供一个自适应垃圾收集的机制,你可以使用这个参数:-XX:+UseAdaptiveSizePolicy 去开启自适应垃圾收集策略,一旦开启自适应策略之后,就不需要设置新生代的大小 Eden 和 Survivor 区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了。虚拟机它会自动地根据系统的运行状况去收集性的监控信息,然后动态地调整这些参数,从而实现最优的停顿时间以及最高的吞吐量。

这种调节方式称为 GC 自适应的调节策略(GC Ergonomics)。自适应调节策略也是 Parallel Scavenge 收集器与 ParNew 收集器的一个重要区别。

所以经过分析,Parallel Scavenge 收集器存在一定的智能性,那么 Parallel Scavenge 适用于什么样的场景呢?它适用于比较注重吞吐量的场景。

简单总结一下本课时,本课时我们主要学习了垃圾收集器相关的术语以及新生代中三个收集器,详细介绍了使用场景、特点以及使用的场景。

以上是关于我们来聊聊垃圾收集器中相关术语与新生代收集器的主要内容,如果未能解决你的问题,请参考以下文章

垃圾收集器与内存分配策略之内存分配与回收策略

JVM之垃圾收集器

搞懂G1垃圾收集器

HotSpot的7种垃圾收集器组合

Hotspot垃圾回收器

垃圾收集器与内存分配策略