Java 垃圾收集算法:Parallel GC


概述

这个组合的垃圾收集器使用标记-复制的方式清理年轻代,使用标记-清除-整理的方式清理老年代。它们都会触发Stop-the-world暂停,挂起应用的全部线程。它们都会使用多线程来运行标记复制/整理,这也是Parallel GC名字的由来。使用这种方法,垃圾收集的次数会明显减少很多。

垃圾收集时使用的线程数可以通过参数-XX:ParallelGCThreads=NNN设置,默认值为系统硬件的 CPU 核心数。

要使用Parallel GC,可以选择下面三种方式之一来启动 JVM。

1
2
3
java -XX:+UseParallelGC com.mypackages.MyExecutableClass
java -XX:+UseParallelOldGC com.mypackages.MyExecutableClass
java -XX:+UseParallelGC -XX:+UseParallelOldGC com.mypackages.MyExecutableClass

在多核 CPU 环境下,如果你得首要目的是增加系统吞吐量,那么Parallel GC非常合适。更高的吞吐量是由更高效的资源利用率带来的:

  • 垃圾收集时,所有的 CPU 核心并行做垃圾清理,带来了更短的暂停时间;
  • 在垃圾收集事件之间,收集器不占用任何资源。

另一方面,因为垃圾收集的所有阶段都不能被打断,Parallel GC还是有可能导致长时间的应用暂停。因此,如果低延时是你的首要目标,那么你应该去看看CMS 垃圾收集算法

让我们来看看使用Parallel GC时,垃圾收集日志长什么样,我们能从中得到哪些信息。下面的日志包含了 2 次垃圾收集事件:Minor GCFull GC

1
2
2015-05-26T14:27:40.915-0200: 116.115: [GC (Allocation Failure) [PSYoungGen: 2694440K->1305132K(2796544K)] 9556775K->8438926K(11185152K), 0.2406675 secs] [Times: user=1.77 sys=0.01, real=0.24 secs]
2015-05-26T14:27:41.155-0200: 116.356: [Full GC (Ergonomics) [PSYoungGen: 1305132K->0K(2796544K)] [ParOldGen: 7133794K->6597672K(8388608K)] 8438926K->6597672K(11185152K), [Metaspace: 6745K->6745K(1056768K)], 0.9158801 secs] [Times: user=4.49 sys=0.64, real=0.92 secs]

Minor GC

下面是第一段日志片段,包含了清理年轻代的垃圾收集事件信息:

2015-05-26T14:27:40.915-0200 : 116.115 : [GC (Allocation Failure) [PSYoungGen : 2694440K->1305132K (2796544K) ] 9556775K->8438926K (11185152K), 0.2406675 secs] [Times: user=1.77 sys=0.01, real=0.24 secs]


  1. 2015-05-26T14:27:40.915-0200 – GC 开始时间。
  2. 116.115 – GC 开始时间,相对于 JVM 的启动时间的偏移,单位秒。
  3. GC – 标志位:Minor GCFull GC。本次为Minor GC
  4. Allocation Failure – 触发垃圾收集的原因。本次为年轻代空间不足以分配新对象。
  5. PSYoungGen – 使用的垃圾收集器名称,代表使用的是并行标记-复制Stop-the-world的垃圾收集器清理年轻代
  6. 2694440K->1305132K – 垃圾收集前后年轻代空间使用量。
  7. (2796544K)年轻代空间大小。
  8. 9556775K->8438926K – 垃圾收集前后堆上空间使用量。
  9. (11185152K) – 堆大小。
  10. 0.2406675 secs – 垃圾收集时长,单位秒。
  11. [Times: user=1.77 sys=0.01, real=0.24 secs] – 分类统计的垃圾收集时长:
    • user:垃圾收集中的线程占用的 CPU 总时间
    • sys:系统调用和等待系统事件占用的 CPU 时间
    • real:应用暂停时长。使用Paralled GC时,,本次 GC 中使用了 8 个线程。要注意,GC 中总有一些操作是不能并行执行的,因此,实际的real值一般会比计算出来的值大一些。

总之,垃圾收集之前堆的使用量为 9,556,775K,其中年轻代使用量为 2,694,440K,那么可算出老年代使用量为 6,862,335K。垃圾收集之后,年轻代使用量下降了 1,389,308K,但堆的使用量只下降了 1,117,849K,意味着有 271,459K的对象从年轻代提升到了老年代

ParallelGC-in-Young-Generation-Java


Full GC

了解了上面清理年轻代的垃圾收集日志,下面通过分析第二段日志看看整个堆是怎么被清理的。

2015-05-26T14:27:41.155-0200 : 116.356 : [Full GC (Ergonomics) [PSYoungGen: 1305132K->0K(2796544K)] [ParOldGen : 7133794K->6597672K (8388608K)] 8438926K->6597672K (11185152K), [Metaspace: 6745K->6745K(1056768K)], 0.9158801 secs, [Times: user=4.49 sys=0.64, real=0.92 secs]


  1. 2015-05-26T14:27:41.155-0200 – GC 开始时间。
  2. 116.356 – GC 开始时间,相对于 JVM 的启动时间的偏移,单位秒。
  3. Full GC – 标志位:Minor GCFull GC。本次为清理年轻代老年代Full GC
  4. Ergonomics – GC 发生的原因。Ergonomics表明 JVM 觉得是时候做些垃圾清理工作了。
  5. [PSYoungGen: 1305132K->0K(2796544K)] – 跟上面的例子类似,它是一个叫PSYoungGen的并发标记-复制Stop-the-world的垃圾收集器。年轻代的使用量降到了 0,这也是Full GC的一般结果。
  6. ParOldGen – 清理老年代的垃圾收集器类型。这是一个叫ParOldGen的并行标记-清除-整理Stop-the-world的垃圾收集器。
  7. 7133794K->6597672K – 垃圾收集前后老年代使用量。
  8. (8388608K)老年代空间大小。
  9. 8438926K->6597672K – 垃圾收集前后堆上空间使用量。
  10. (11185152K) – 堆大小。
  11. [Metaspace: 6745K->6745K(1056768K)] – 垃圾收集前后元数据区使用量。这里没有变化。
  12. 0.9158801 secs – 垃圾收集时长,单位秒。
  13. [Times: user=4.49 sys=0.64, real=0.92 secs] – 分类统计的垃圾收集时长:
    • user:垃圾收集中的线程占用的 CPU 总时间
    • sys:系统调用和等待系统事件占用的 CPU 时间
    • real:应用暂停时长。使用Paralled GC时,,本次 GC 中使用了 8 个线程。要注意,GC 中总有一些操作是不能并行执行的,因此,实际的real值一般会比计算出来的值大一些。

Full GCMinor GC的区别很明显 – 除了年轻代老年代元数据区同样被清理了。下图示意了Full GC前后内存使用情况。

Java-ParallelGC-in-Old-Generation

原文地址:GC Algorithms: Parallel GC


相关文章