java中的串行并行及CMS垃圾回收器是怎样的

本篇文章给大家分享的是有关java中的串行并行及CMS垃圾回收器是怎样的,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。

站在用户的角度思考问题,与客户深入沟通,找到安庆网站设计与安庆网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:网站设计制作、网站建设、企业官网、英文网站、手机端网站、网站推广、主机域名、网络空间、企业邮箱。业务覆盖安庆地区。

1. 串行回收器: SerialGC

串行回收器是指使用单线程的回收器。每次回收器,串行回收器只有一个工作线程,对于并行能力较弱的计算机来说,串行回收器的专注性和独占性往往能让其有更好的性能表现。

串行回收器可以在新生代和老年代使用,根据不同的堆空间分为新生代串行回收器和老年代串行器。

1.1 新生代串行回收器:SerialGC

串行回收器主要有两个特点:

  • 仅仅使用单线程进行垃圾回收

  • 独占式的垃圾回收方式

使用 -XX:+UseSerialGC 参数可以指定使用新生代串行回收器或老年代串行回收器。当虚拟机在Client模式下运行时,它是默认的垃圾回收器。

1.2 老年代串行回收器:SerialOldGC

老年代串行回收器使用的标记压缩法,和新生代串行回收器一样,它也是一个串行的独占式的垃圾回收器。由于老年代垃圾回收通常会需要比新生代垃圾回收更长的时间,在堆空间较大的应用程序中,一旦老年代串行回收器启动,应用程序很可能会停顿较长的时间。

若要开启老年代串行回收器,可以尝试使用以下参数

  • -XX:+UseSerialGC:新生代老年代都使用串行回收器

  • -XX:+UseParNewGc:新生代使用parNew回收器,老年代使用串行回收器,jdk9、jdk10已删除该参数,因为ParNew需要和CMS搭配工作,而CMS已经被G1替代,不再支持此参数。

  • -XX:+UseParallelGC:新生代使用ParallelGC回收器,老年代使用串行回收器。

1.3 回收示例

示例代码:

public class Demo02 {
    public static void main(String[] args) {
        byte[] b = null;
        for(int i = 0; i < 10; i++) {
            b = new byte[2 * 1024 * 1024];
        }
    }
}

使用参数 -Xmx10m -Xms10m -Xmn2m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC 运行,gc 日志如下:

//回收新生代
[GC (Allocation Failure) [DefNew: 1024K->337K(1536K), 0.0007683 secs] 1024K->337K(9728K), 0.0007883 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//回收新生代与老年代
[GC (Allocation Failure) [DefNew: 645K->37K(1536K), 0.0008160 secs][Tenured: 6470K->2410K(8192K), 0.0015724 secs] 6789K->2410K(9728K), [Metaspace: 2952K->2952K(1056768K)], 0.0024163 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
// 省略其他日志...

日志中的 DefNew 表示新生代,Tenured 表示老年代。

2. 并行回收器

并行回收器在串行回收器的基础上做了改进,它使用了多个线程同时进行垃圾回收。对于并行能力强的计算机,可以有效减少垃圾回收所需要的时间。

2.1 SerialGC的多线程版本:ParNew 回收器

ParNew 回收器是一个工作在新生代的垃圾回收器。

它只是简单地将串行回收器多线程化,它的回收策略、算法及参数和新生代串行回收器一样。在并发能力比较强的cpu上,它产生的停顿时间要短于串行回收器,而单cpu或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。

开启 ParNew 回收器可以使用以下参数:

  • -XX:+UseParNewGC:新生代使用ParNew回收器,老年代使用串行回收器。jdk9、jdk10中已经删除,因为ParNew需要和CMS搭配工作,而CMS已经被G1替代,不再支持此参数。

  • -XX:+UseConcMarkSweepGC:新生代使用ParNew回收器,老年代使用CMS.jdk9、jdk10中不建议使用,建议使用默认的G1垃圾回收器。

ParNew回收器工作时的线程数可以使用-XX:ParallelGCThreads参数指定。一般,最好与CPU数量相当,避免过多的线程数影响垃圾回收性能。在默认情况下,当CPU数量小于8时,ParallelGCThreads的值等于CPU数量,当CPU数量大于8时,ParallelGCThreads的值等于3+((5xCPU_Count)/8)(CPU数量为16时,ParallelGCThreads的值为13)

示例代码:

public class Demo02 {
    public static void main(String[] args) {
        byte[] b = null;
        for(int i = 0; i < 10; i++) {
            b = new byte[2 * 1024 * 1024];
        }
    }
}

使用参数 -Xmx10m -Xms10m -Xmn2m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseParNewGC 运行,gc 日志如下:

[GC (Allocation Failure) [ParNew: 1024K->384K(1536K), 0.0004642 secs] 1024K->384K(9728K), 0.0004796 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 691K->265K(1536K), 0.0009477 secs][Tenured: 6509K->2407K(8192K), 0.0012491 secs] 6835K->2407K(9728K), [Metaspace: 2933K->2933K(1056768K)], 0.0022175 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 41K->45K(1536K), 0.0006914 secs][Tenured: 6503K->2407K(8192K), 0.0012305 secs] 6544K->2407K(9728K), [Metaspace: 2936K->2936K(1056768K)], 0.0019425 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
// 省略其他日志...

可以看到,这个输出和新生代串行回收器的输出几乎一致,只有回收器标识符不同。

2.2 关注吞吐量的回收器:ParallelGC

ParallelGC是一个既可以工作在新生代,又可以工作在老年代的垃圾回收器。工作在新生代时,叫ParallelGC,工作在老年代时叫ParallelOldGC

2.2.1 新生代 ParallelGC 回收器

新生代 ParallelGC 回收器也是使用复制算法的回收器。从表面上看,它和ParaNew回收器一样,都是多线程、独占式的回收器,但是Parallel回收器有一个重要的特点:它非常关注系统的吞吐量。

新生代Parallel回收器可以使用以下参数启用:

  • -XX:+UseParallelGC:新生代使用ParallelGC回收器,老年代使用串行回收器。

  • -XX:+UseParallelOldGC:新生代使用ParallelGC回收器,老年代使用ParallelOldGC回收器。

ParallelGC回收器提供了两个重要的参数用于控制系统的吞吐量:

  • -XX:MaxGCPauseMillis:设置最大垃圾回收停顿时间。它的值是一个大于0的整数。ParallelGC 在工作时,会调整java堆大小或者其他参数,尽可能指导停顿时间控制在MaxGCPauseMillis以内。

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

除此之外,ParallelGC回收器还支持一种自适应的GC调节策略,使用-XX:+UseAdaptiveSizePolicy可以打开自适应策略。在这种模式下,新生代的大小、eden区和survivor区的比例、晋升老年代的对象年龄等参数会被自动调整,以达到堆大小、吞吐量和停顿时间之间的平衡点。

2.2.2 老年代 ParallelOldGC 回收器

老年代ParallelOldGC回收器也是一种多线程并发的回收器。和新生代ParallelGC回收器一样,它也是一种关注吞吐量的回收器。

老年代ParallelOldGC可以使用以下参数启用:

  • -XX:+UseParallelOldGC:新生代使用ParallelGC回收器,老年代使用ParallelOldGC回收器。

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

2.2.3 回收示例

示例代码:

public class Demo02 {
    public static void main(String[] args) {
        byte[] b = null;
        for(int i = 0; i < 10; i++) {
            b = new byte[2 * 1024 * 1024];
        }
    }
}

使用参数 -Xmx10m -Xms10m -Xmn2m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseParallelOldGC 运行,gc 日志如下:

//回收年轻代
[GC (Allocation Failure) [PSYoungGen: 464K->432K(1536K)] 6616K->6584K(9728K), 0.0013387 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
//回收老年代
[Full GC (Allocation Failure) [PSYoungGen: 432K->0K(1536K)] [ParOldGen: 6152K->2407K(8192K)] 6584K->2407K(9728K), [Metaspace: 2928K->2928K(1056768K)], 0.0032880 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2.3 CMS回收器(jdk8及之前的版本)

ParallelGCParallelOldGC 不同,CMS (Concurrent Mark Sweep,意为并发标记清除) 回收器主要关注系统停顿时间。它是一个老年代垃圾回收器。

CMS主要工作步骤如下:

  1. 初始标记(CMS-initial-mark):标记根对象(STW)

  2. 并发标记(CMS-concurrent-mark):标记所有对象

  3. 预清理(MS-concurrent-preclean):清理前准备及控制停顿时间

  4. 重新标记(CMS-remark):修正并发标记数据(STW)

  5. 并发清理(CMS-concurrent-sweep):清理垃圾

  6. 并发重置(CMS-concurrent-reset):垃圾回收完成后,重新初始化CMS数据,为下一次垃圾回收做准备。

以上过程中,并发标记并发清理并发重置都是可以和应用线程一起执行的。

CMS 回收器的主要参数如下:

  • 启动CMS垃圾回收器:-XX:+UseConcMarkSweepGC

  • 关闭预清理:-XX:-CMSPrecleaningEnabled。在整个CMS的回收过程中,默认情况下,在并发标记之后,会有一个预清理的操作,预清理是并发的,除了为正式清理做准备检查,还会尝试控制一次停顿时间。

  • 设置CMS的并发线程数:CMS默认启动的回收线程数目是 (ParallelGCThreads + 3)/4) ,如果你需要明确设定,可以通过-XX:ParallelCMSThreads=20来设定,其中ParallelGCThreads是年轻代的并行收集线程数.

  • 指定内存回收的阈值:由于CMS不是独占式的,在回收过程中,应用程序仍然在不停地工作。在应用程序工作的过程中,又会不断地产生垃圾,这些新垃圾在当前的回收过程中是无法清除的。同时,因为应用程序没有中断,所以在CMS回收过程中,还应该确保应用程序有足够的内存可用。因此,CMS回收器不会等到堆内存饱和时才进行垃圾回收,而当堆内存使用率达到某一阈值时便开始进行回收。这个回收阈值可以通过参数-XX:CMSInitiatingOccupancyFraction指定,默认是68,即老年代空间使用率达到68%时,会执行一次CMS回收。

  • 开启碎片整理:CMS是不会整理堆碎片的,因此为了防止堆碎片引起full gc,通过会开启CMS阶段进行合并碎片选项:-XX:+UseCMSCompactAtFullCollection,内存碎片的整理不是并发进行的,因此性能上会有所消耗。

  • 指定多少次CMS回收后,进行一次内存压缩:-XX:CMSFullGCBeforeCompaction

  • 回收Perm区:CMS默认不回收Perm区的,如果Perm区满了,会触发一次FullGC。如果希望使用CMS回收Perm区,可以使用参数-XX:+CMSClassUnloadingEnabled

示例代码:

public class Demo02 {
    public static void main(String[] args) {
        byte[] b = null;
        for(int i = 0; i < 10; i++) {
            b = new byte[2 * 1024 * 1024];
        }
    }
}

使用参数 -Xmx10m -Xms10m -Xmn2m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC 运行,gc 日志如下:

[GC (Allocation Failure) [ParNew: 1024K->362K(1536K), 0.0013608 secs] 1024K->362K(9728K), 0.0014303 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 730K->264K(1536K), 0.0028882 secs][CMS: 6490K->2418K(8192K), 0.0025366 secs] 6874K->2418K(9728K), [Metaspace: 3008K->3008K(1056768K)], 0.0054949 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 4466K(8192K)] 4466K(9728K), 0.0004792 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[GC (Allocation Failure) [ParNew: 41K->55K(1536K), 0.0008476 secs][CMS[CMS-concurrent-mark: 0.002/0.003 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
 (concurrent mode failure): 6514K->2418K(8192K), 0.0035573 secs] 6555K->2418K(9728K), [Metaspace: 3016K->3016K(1056768K)], 0.0044327 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [ParNew: 41K->11K(1536K), 0.0008080 secs][CMS: 6514K->2419K(8192K), 0.0013165 secs] 6555K->2419K(9728K), [Metaspace: 3026K->3026K(1056768K)], 0.0021484 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 4467K(8192K)] 4467K(9728K), 0.0002437 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[GC (Allocation Failure) [ParNew: 20K->4K(1536K), 0.0006944 secs][CMS[CMS-concurrent-mark: 0.001/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 (concurrent mode failure): 6515K->2419K(8192K), 0.0023357 secs] 6536K->2419K(9728K), [Metaspace: 3037K->3037K(1056768K)], 0.0030568 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

相关信息说明如下:

  • CMS Initial Mark:初始标记

  • CMS-concurrent-mark:并发标记

  • CMS-concurrent-preclean-start:预清理

  • CMS-concurrent-abortable-preclean: 可中止的预清理,等待一次新生代GC再进行后续操作。预清理后是重新标记阶段,由于重新标记是独占CPU的,如果新生代GC发生后,立即触发一次重新标记,那么一次停顿的时间可能会很长,为了避免这种情况,预处理会刻意等待一次新生代GC的发生,然后根据历史性能数据预测下一次新生代GC可能发生的时间,在当前时间和预测时间的中间时刻进行重新标记,这样尽量避免新生代GC和重新标记重合,尽可能减少一次停顿时间。

  • CMS-remark:重新标记

  • CMS-concurrent-sweep:并发清除

  • CMS-concurrent-reset:并发重置

  • concurrent mode failure:CMS回收器并发回收失败,这很可能是应用程序在运行过程中,垃圾未回收完成而新垃圾又在产生,导致老年代空间不够造成的,可以考虑增加老年代空间,或者设置一个较小的-XX:CMSInitiatingOccupancyFraction参数。注意:如果在CMS的执行过程中,已经出现了内存不足的情况,CMS回收就会失败,虚拟机将启动老年代串行回收器进行垃圾回收,此时应用程序将完全中断,直到垃圾回收完成,因此应尽量避免这种情况发生。

以上就是java中的串行并行及CMS垃圾回收器是怎样的,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注创新互联行业资讯频道。


新闻名称:java中的串行并行及CMS垃圾回收器是怎样的
当前地址:http://myzitong.com/article/jpjhpp.html