Serial&Parallel&CMS&G1

    科技2022-07-12  148

    G1:https://yuanyu.blog.csdn.net/article/details/108917824jstat&jmap&jhat:https://yuanyu.blog.csdn.net/article/details/106823714 λ java -version java version "1.8.0_241" Java(TM) SE Runtime Environment (build 1.8.0_241-b07) Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)

    1 垃圾收集器概述

    Java应用启动的时候,除了配置Xms以及Xmx参数,还需要选择合适的垃圾收集器。截止JDK1.8,共提供了7款垃圾收集器,每一款垃圾收集器都具有不同的特点。我们所需要做的就是,根据Java应用的特点已经部署环境,确定不同垃圾收集器的组合。这几款垃圾收集器之间联系如下图所示:

    由上图可知,Serial、ParNew、Parallel Scavenge主要负责Young generation区域的垃圾回收。CMS、Serial Old、Parallel Old主要负责Tenured generation区域的垃圾回收,G1在Young generation以及Tenured generation区域均可以使用。 


    2 四种类型的垃圾收集器

    jdk提供了多重垃圾收集器,下文会提供主流的垃圾收集器搭配组合,各种组合按照特点分为以下三类

    串行收集器:Serial + Serial Old并行收集器: Parallel Scavenge + Parallel Old,专注于应用吞吐量并发收集器:CMS、G1,专注于响应时间

    2.1 串行垃圾回收器(Serial)

    Serial收集器(Serial + Serial Old)的主要特点是单线程回收资源。当需要执行垃圾回收时,程序会暂停一切工作(又称为Stop The World,STW),使用复制算法完成垃圾清理工作。

    ParNew收集器是Serial收集器的多线程版本,除了使用多线程进行垃圾收集工作,其他的控制参数,收集算法,对象分配规则等均与Serial收集器一致。ParNew收集器在单核/双核环境下,效率未必有Serial收集器工作效率高(多线程切换开销等因素限制),当然随着核数的增加,其性能也会得到较大的提升。

    2.2 并行垃圾回收器(Parallel)

    Parallel收集器(Parallel Scavenge + Parallel Old)相比于Serial收集器的主要特点是,其是通过多线程完成垃圾的清理工作。其中Parallel Scavenge使用复制算法完成垃圾收集(Parallel Old使用标记整理算法),如果从这一点看其与ParNew相似,但实际上两者的出发点存在区别,区别如下所示:

    ParNew出发点在于加速资源回收的速度,以减少应用的STW时间Parallel Scavenge出发点在于资源回收的吞吐量,吞吐量 = 用户线程时间/(用户线程时间 + GC线程时间)

    2.3 并发垃圾回收器(CMS)

    CMS(Concurrent Mark Sweep)收集器是jdk 1.5推出的第一款真正意义上的并发收集器(针对老年代),实现了让垃圾收集器与用户线程(近似)同时工作。

    2.4 并发垃圾回收器(G1)

    https://yuanyu.blog.csdn.net/article/details/108917824


    3 查看当前JDK默认的垃圾收集器

    java -XX:+PrintCommandLineFlags -version


    4 垃圾收集器日志输出

    设置了新生代老年代会自动被激活成对应的,同理设置了老年代就会激活对应的新生代,不要在添加一个参数去设置新生代或者老年代

    垃圾收集器日志输出关键字 类型作用范围算法SerialDefNewDefault New Generation串行新生代复制ParNewParNewParallel New Generation

    并行

    新生代复制Parallel ScavengePSYoungGenParallel Scavenge

    并行

    新生代复制Serial OldTenuredOld串行老年代标记-压缩Parallel OldParOldGenParallel Old Generation并行老年代标记-压缩CMSCMS 并发老年代标记-清除


    5 设置新生代垃圾回收器

    命令组合新生代+老年代-Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGCDefNew+Tenured串行+串行-Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseParNewGCParNew+Tenured并行+串行-Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseParallelGCPSYoungGen+parOldGen并行+并行

    5.1 串行GC(Serial)/(Serial Copying)

    Serial收集器(串行收集器),一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。

    串行收集器是最古老,最稳定以及效率高的收集器,只使用一个线程去回收但其在进行垃圾收集过程中可能会产生较长的停顿( "Stop-The-Word" 状态)。虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。 

    对应JVM参数是:-XX:+UseSerialGC开启后会使用:Serial(Young区、DeFNew) + Serial Old(Old区、Tenured) 的收集器组合新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法 /** * -Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC */ // 新生代和老年代都串行,新生代复制算法,老年代 标记-整理算法 public class Main { public static void main(String[] args) throws InterruptedException, IOException { String str = ""; try { while (true) { str += str + new Random().nextInt(7777777) + new Random().nextInt(88888888); str.intern(); } } catch (Throwable e) { e.printStackTrace(); } } }

    5.2 并行GC(ParNew)

    ParNew并行收集器,使用多线程进行垃圾回收,在垃圾收集时,会 "Stop-the-World" 暂停其他所有的工作线程直到它收集结束。

    ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Seria收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。它是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器

    常用对应JVM参数: -XX:+UseParNewGC 启用ParNew收集器,只影响新生代的收集,不影响老年代 开启上述参数后,会使用 ParNew(Young区、ParNew)+Serial Old(Old区、Tenured)的收集器组合,新生代使用复制算法,老年代采用标记-整理算法 /** * -Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseParNewGC */ // 新生代并行,老年代串行,新生代复制算法,老年代 标记-整理算法 public class Main { public static void main(String[] args) { String str = ""; try { while (true) { str += str + new Random().nextInt(7777777) + new Random().nextInt(88888888); str.intern(); } } catch (Throwable e) { e.printStackTrace(); } } }

    java8默认的就是这个 

    运行上面代码会发现警告信息,Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release,ParNew+Tenured(Serial Old)这样的搭配,java8已经不再被推荐。

    备注: -XX:ParallelGCThreads 限制线程数量,默认开启和CPU数目相同的线程数

    5.3 并行回收GC(Parallel)/(Parallel Scavenge)

    Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。串行收集器在新生代和老年代的并行化

    它重点关注的是可控制的吞吐量 (Thoughput=运行用户代码时间/(运行用户代码时间+垃圾收集时间) ,也即比如程序运行100分钟,垃圾收集时间1分钟,吞吐量就是99%)。高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务。

    自适应调节策略也是ParallelScavenge收集器与ParNew收集器的一个重要区别。(自适应调节策略: 虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMillis)或最大的吞吐量。

    常用JVM参数: -XX:+UseParallelGC -XX:+UseParallelOldGC(可互相激活,设置一个即可),使用Parallel Scanvenge收集器开启该参数后: 新生代使用复制算法,老年代使用标记-整理算法

    备注:-XX:ParallelGCThreads=数字N 表示启动多少个GC线程 cpu>8 N=5/8 cpu<8 N=实际个数

    /** * -Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseParallelGC */ public class Main { public static void main(String[] args) { String str = ""; try { while (true) { str += str + new Random().nextInt(7777777) + new Random().nextInt(88888888); str.intern(); } } catch (Throwable e) { e.printStackTrace(); } } }


    6 设置老年代垃圾回收器

    参数组合-Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseParallelOldGCPSYoungGen+ParOldGen-Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseConcMarkSweepGCParNew+CMS+Tenured

    6.1 串行GC(Serial Old)/(Serial MSC)

    Serial Old是Serial垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的年老代垃圾收集器。

    在Server模式下,主要有两个用途(了解,版本已经到8及以后)

    JDK1.5之前版本中与新生代的Parallel Scavenge收集器搭配使用。(Parallel Scavenge +Serial Old )作为老年代版中使用CMS收集器的后备垃圾收集方案

    6.2 并行GC(Parallel Old)/(Parallel MSC)

    Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,Parallel Old收集器在JDK1.6才开始提供。

    在JDK1.6之前,新生代使用Parallel Scavenge收集器只能搭配年老代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。在JDK1.6之前(Parallel Scavenge + Serial Old )

    Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8后可以优先考虑新生代Parallel Scavenge和年老代Parallel Old 收集器的搭配策略。在JDK1.8及后(Parallel Scavenge +Parallel Old )

    JVM常用参数: -XX:+UseParallelOldGC设置该参数后,新生代Parallel+老年代Parallel Old /** * -Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseParallelOldGC */ public class Main { public static void main(String[] args) { String str = ""; try { while (true) { str += str + new Random().nextInt(7777777) + new Random().nextInt(88888888); str.intern(); } } catch (Throwable e) { e.printStackTrace(); } } }

    6.3 并发标记清除GC(CMS)

    CMS收集器是一种以获取最短回收停顿时间为目标的收集器。适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。

    Concurrent Mark Sweep 并发标记清除,并发收集低停顿,并发指的是与用户线程一起执行

    开启该收集器的JVM参数 : -XX:+UseConcMarkSweepGC开启该参数后会自动将 -XX:+UseParNewGC 打开开启该参数后,使用ParNew(Young区)+CMS(Old区)+Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器

    初始标记(CMS initial mark),只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程并发标记(CMs concurrent mark) ,进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象重新标记(CMS remark),为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正并发清除(CMS concurrent sweep) 清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户线程一起并发工作,所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。

    优点:并发收集低停顿

    缺点:

    并发执行,对CPU资源压力大,采用的标记清除算法会导致大量碎片由于并发进行,CMS在收集与应用线程会同时增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间。标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数 -XX:CMSFullGCsBeForeCompaction(默认O,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。 /** * -Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC */ public class Main { public static void main(String[] args) { String str = ""; try { while (true) { str += str + new Random().nextInt(7777777) + new Random().nextInt(88888888); str.intern(); } } catch (Throwable e) { e.printStackTrace(); } } }


    7 如何选择垃圾收集器

    单CPU或小内存,单机程序-XX:+UseSerialGC多CPU,需要最大吞吐量,如后台计算型应用

    -XX:+UseParallelGC

    或者 -XX:+UseParallelOldGC

    多CPU,追求低停顿时间,需快速响应如互联网应用-XX:+UseConcMarkSweepGC

     

     

    如果同时指定两个,只是演示工作中不能这样设置

    /** * 关联激活,配置一个就可以了,不要化蛇填足 * -Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseParallelGC -XX:+UseParallelOldGC (PSYoungGen+ParOldGen) * -Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseParNewGC -XX:+UseConcMarkSweepGC (par new generation + concurrent mark-sweep generation) */ public class Main { public static void main(String[] args) { String str = ""; try { while (true) { str += str + new Random().nextInt(7777777) + new Random().nextInt(88888888); str.intern(); } } catch (Throwable e) { e.printStackTrace(); } } }

     

    https://blog.csdn.net/sunjin9418/article/details/79603651https://zhuanlan.zhihu.com/p/133694190https://www.cnblogs.com/jason1990/p/11732261.html
    Processed: 0.011, SQL: 8