【JVM四】老年代垃圾回收:吞吐量垃圾收集器(Throughput GC)

吞吐量与用户线程暂停时间

 

衡量垃圾回收算法优劣的指标有两个:

  • 吞吐量越高,则算法越好
  • 暂停时间越短,则算法越好

首先说明吞吐量和暂停时间的含义。

 

垃圾回收时,JVM会启动几个特定的GC线程来完成垃圾回收的任务,这些GC线程与应用的用户线程产生竞争关系,共同竞争处理器资源以及CPU的执行时间。GC线程不会对用户带来的任何价值,因此,好的GC应该占用资源少,执行快,在不显著影响用户线程的情况下,迅速完成垃圾回收任务。

 

吞吐量是针对用户线程而言的,具体指的是在一个给定的时间段内,用户线程运行的时间所占的百分比(在这段时间内,GC运行也要占用CPU的运行时间)。比如,吞吐量99/100表示平均100秒的时间段内,有99秒在运行用户程序,而有1秒在运行GC线程,

 

暂停的含义是指,在一个给定的时间段内,应用线程由于GC而完全暂停的时间。比如,假如一次GC产生了100毫秒的用户线程暂停时间,这意味着在这100毫米里,用户线程完全处于不活跃状态。平均暂停时间是指在某个时间段内,用户线程在每次GC过程产生的暂停时间的平均值,最大暂停时间是在某个时间段内,用户线程在每次GC过程中产生暂停时间的最大值。

 

吞吐量 vs 用户线程暂停时间

高吞吐量和低暂停时间是应用所希望的,因为运行JVM不是为了运行垃圾回收线程的,而是能带来价值的应用系统。另外,平均暂停时间可能比较小,但是最大暂停时间却可能比较大,对于交互式应用程序,最大暂停时间应该比较小。

 

然而,高吞吐量和低暂停时间是竞争关系。比如,为了避免GC线程潜在的导致与用户线程发生线程同步和数据不一致问题,这要求GC线程在决定哪些对象可以回收,哪些对象仍然不能回收(仍然被活下来的对象引用)时的过程中,用户线程不能修改对象的状态,基于此,用户线程在GC过程中必须停下来(更确切的说,根据所用GC算法不同,用户线程可能在一次GC的某几个阶段停下来而不是完全暂停,比如CMS垃圾收集,一次GC分6个阶段,但是只有两个阶段需要暂停用户线程)。不仅如此,线程切换也会带来额外的开销:上下文切换带来的直接开销,缓存作用带来的间接开销(causes additional costs for thread scheduling: direct costs through context switches and indirect costs because of cache effects),在加上JVM内部的安全感考量,这意味着GC不仅仅带来GC线程本身的开销,还额外的增了一些开销,这些开销对于应用来说,都是"无价值"开销。因此,为了获得最大吞吐量,JVM必须尽可能少的运行GC,只有在迫不得已的情况下(比如新生代或者老年代已经满了)才运行GC。但是,这种方式也有问题,只在不得已的情况下才运行GC,那么每次运行GC时,需要做的事情会很多,比如有更多的对象积累在堆上等待回收,每次的GC时间会很高,由此引起的平均和最大暂停时间也会很高,这就要求GC不能在迫不得已的情况才运行,这回到了刚才的问题。

 

当设计GC算法或者选用给予某种GC算法的垃圾收集器时,我们要根据应用的特点选择合适的收集器,每个垃圾收集器要么只是把吞吐量和暂停时间两个目标之一作为它的设计目标,要么在两个之间做一个折中而兼顾两个目标。

 

Sun/Oracle HotSpot JVM的垃圾回收

对于老年代的垃圾回收,HotSpot JVM有两类垃圾回收算法。第一类算法目标是最大化吞吐量(面向吞吐量的垃圾回收),第二类算法目标在于缩短等待时间。第一类垃圾回收在HotSpot JVM中称为Throughput Collector。

 

吞吐量收集器的工作方式

吞吐量垃圾回收实在老年代没有足够的空间来分配对象时进行触发(分配给老年代的对象大多是从新生代升级到老年代的对象)。

1.从 "GC roots"开始,GC在老年代堆空间上查找可达对象,并将它们标记为存活状态。

2. GC把老年代上标记为存活状态的对象移动到一端,因此,这些存活的对象会占用单块连续的内存空间。这里采用的是对象移动(Move)的方式,而不是复制(copy)到另外的堆空间,因此,可以解决碎片问题。

3.将剩下的堆空间标记位可回收状态

4.吞吐量收集器使用一个或者几个GC线程来执行线程操作。当使用多个线程来进行垃圾回收时,算法分解为多个不同步骤,每个步骤由不同的GC线程进行处理,因此这些GC线程不会相互干扰,只负责自己的那块内存空间。

5.在GC过程中,所有的用户线程都会暂停。当GC结束,JVM会恢复用户线程的执行

 

 

与吞吐量相关的JVM选项

 

 

-XX:+UseSerialGC

 

使用这个选项指示JVM启动单线程(串行)的面向吞吐量的垃圾收集器。新生代和老年代都是使用单线程进行垃圾回收。这个选项通常只用于单核CPU的情况,如果只有单核而使用多线程并发的进行垃圾回收,由于频繁的线程切换,反而降低了效率

 

-XX:+UseParallelGC

 

使用这个选项指示JVM启动多个GC线程,并行的进行新生代的垃圾回收。对于Java6,使用-XX:+UseParallelOldGC更可取。而到了Java7,使用-XX:+UseParallelGC和使用-XX:+UseParallelOldGC一样的效果

 

 

-XX:+UseParallelOldGC

 

这里的Old不是老的GC算法,而是指老年代,但是这个选项的名字及其容易产生误解,以为只是对老年代进行并行回收。实际上,-XX:+UseParallelOldGC不但对老年代进行并行回收,还会对新生代进行并行的垃圾回收。如果是多核CPU并且高吞吐量是一个希望的目标,那么应该加上这个选项。吞吐量浏览器之所以称为吞吐量浏览器

原因就在于它把高吞吐作为这个算法的卖点。

 

 

 XX:ParallelGCThreads

 

使用-XX:ParallelGCThreads=<value>选项可以用来设定进行并行GC的线程数。例如,-XX:ParallelGCThreads=6表示使用6个线程来并行的执行GC任务。

JVM设置的默认值gcThreadNum计算方法是基于CPU的内核数,

N=Runtime.availableProcessors();

if (N <= 8) {

   gcThreadNum = N;

} else {

  gcThreadNum = 3 + 5*N/8;

}

通常,可以使用JVM提供的默认值,但是如果一台服务器上有多个JVM进程,那么应该将值设置的小一些以避免多个JVM进程的GC线程过多导致的并发竞争。此时的N就是CPU内核数除以JVM进程数

 

-XX:UseAdaptiveSizePolicy

吞吐量垃圾回收提供了一个很有趣但是在现代JVM上很普遍的特性以提升GC配置的用户友好性。这个特性是"ergomics"的一部分。"ergomics"这个概念是Sun HotSpot JVM在Java5引进的概念。它使得GC回收器能够动态自适应的做出是否要应用用户所做的参数设置,比如堆区的大小以及GC的设置。如果JVM能够判定,应用用户所作的设置能够提升GC性能则应用,否则继续采用默认值。"提升GC性能"的含义是由用户通过选项-XX:GCTimeRatio and -XX:MaxGCPauseMillis进行设置的(这两个选项就是关于暂停时间和吞吐量这两个衡量GC算法的指标的设置)。

 

"ergomics"是默认开启的,JVM GC自适应的特性是现代JVM最强大的功能之一。不过,有时候,我们可能能够确切的知道对一个具体的应用什么样的设置是最好的,在这种情况下,我们不希望JVM加入自适应的判断逻辑,那么可以使用将这个选项-XX:-UseAdaptiveSizePolicy去掉

 

 

-XX:GCTimeRatio

使用 -XX:GCTimeRatio=<value>,我们是告诉JVM,我们所希望达到的吞吐量目标。更确切的说,以-XX:GCTimeRatio=N为例,N/N+1即是应用线程的CPU运行时间占(应用线程运行时间+GC线程运行时间)的比例。比如N=99,表示用户线程的运行是99/100,而GC线程则占1/100。

那么,JVM在运行时,会自动的调整堆和GC的配置以达到这个目标。-XX:GCTimeRatio的默认值是99,即99%的时间运行用户线程,1%的时间运行应用线程。

 

-XX:MaxGCPauseMillis

 

-XX:MaxGCPauseMillis=<value>这个选项是要告诉JVM用户线程所希望的最大的暂停时间(ms为单位)。JVM在运行时,吞吐量垃圾回收器会进行暂停时间统计(基于加权平均和标准方差)

如果统计结果表明,GC导致的暂停时间有可能超过用户所希望的最大暂停时间,那么JVM会动态调整堆和GC的设置来降低最大等待时间。

 

需要注意的是,

1. 新生代和老年代的垃圾回收分别统计计算

2. 默认情况下,JVM没有设置最大的暂停时间

3.如果 -XX:GCTimeRatio=<value>和-XX:MaxGCPauseMillis=<value>这两个竞争的目标都设置了,那么-XX:MaxGCPauseMillis=<value>有更高的优先级,即-XX:GCTimeRatio=<value>将被忽略

 

4. 当设置-XX:MaxGCPauseMillis=<value>时,需要注意,这个值不能设置的过小,因为为了降低最大暂停时间,JVM会增加GC的数量(频繁GC),这将严重的影响吞吐量(相当于把最大时间分解到多次GC中了)

对于暂停时间少这个目标要求很高的话,比如web交互式应用,那么吞吐量GC不是很合适,应该选用CMS垃圾收集器。

 

本文参考:https://blog.codecentric.de/en/2013/01/useful-jvm-flags-part-6-throughput-collector/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(垃圾回收)