JVM系列之垃圾收集器

  

目录

一、概念理解

   1. 并发和并行

  2. Minor GC Major GC 和 Full GC

  3. 吞吐量

  4. client模式 和 server模式

二、串行分类器(Serial)

三、并行分类器(Parallel/Throughput)

四、CMS收集器(讲透CMS收集器原理)

回收过程(七步)

五、G1收集器(讲透G1GC收集器原理)

六、ZGC收集器(讲透ZGC收集器原理)

七、调优参数

八、总结


Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。



图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。



一、概念理解



   1. 并发和并行

  • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。   
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。  

  2. Minor GC Major GC 和 Full GC

  • Minor GC:指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。   
  • Major GC:指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。  
  • Full GC:收集整个java堆和方法区的垃圾收集,触发条件:
    (1)   调用system.gc()时,系统建议执行Full Gc,但是不必然执行
    (2)老年代空间不足
    (3)方法区空间不足
    (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存  

  3. 吞吐量

    吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。
虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

  4. client模式 和 server模式

  • client模式:应用于桌面应用程序。-client 参数指定启动java程序。优点:启动速度快。   
  • server:应用于服务端应用程序。-server 参数指定启动java程序。特点:启动慢,运行快。  

二、串行分类器(Serial)


 


 

  1. 参数:-XX:+UseSerialGC开启串行收集器
  2. 特性:这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。Stop The World
  3. 应用场景:Serial收集器是虚拟机运行在Client模式下的默认新生代收集器,最适用于应用程序的内存使用少于100MB的场景(少于100MB,并行和并发收集器起不了太大的性能提升)。
  4. 优势:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。


三、并行分类器(Parallel/Throughput

  Parallel Scavenge收集器 ,ParNew收集器,Parallel Old收集器


 

  1. 参数:-XX:+UseParallelGC新生代开启Parallel Scavenge收集器,-XX:+UseParallelOldGC新生代和老年代开启Parallel Scavenge收集器,
  2. 特性:Throughput收集器使用多线程回收新生代空间和老年代空间,GC速度比Serial收集器快得多,Throughput收集器在Minor GC 和 Full GC时会暂停所有应用线程 ,直到它收集结束。Stop The World
  3. 应用场景:Throughput收集器是Server级虚拟机(多CPU的Unix机器以及任何64位虚拟机)的默认收集器,停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
  4. 优势:使用Throughput收集器处理应用程序线程的批量任务能最大程度的利用CPU的处理能力,通常能获得更好的性能。

四、CMS收集器(讲透CMS收集器原理

  

回收过程(七步)

  1. 初始标记(initial-mark):
    应用程序需要停顿,主要是收集GC roots直接引用的对象并标记这些对象为存活对象,这些对象可能在新生代或者老年代。除了被 GC-Root 引用外,老年代中的对象如果仅被年轻代中的对象引用,他也是不能回收的,因此,在上述以 GC-Roots 为起点进行的标记完成后,还需要遍历新生代对象,标记可达的老年代对象。不过由于直接引用的对象较少,故不需要消耗很长的时间;
  2. 并发标记(concurrent-mark):
    与应用程序并发执行,从初始标记阶段所收集到的GC roots和年轻代直接引用的对象出发,继续扫描查找和标记可达的对象,这个过程涉及到整个堆,即包括新生代和老年代,故需要耗费较长时间,所以与应用程序并发执行,不会对应用程序造成干扰;

    2.1 如何处理并发标记阶段的引用关系变更
    由于在并发标记阶段,用户线程与回收线程并发执行,随时可能有新生代的对象晋升到老年代、直接在老年代分配对象或者引用关系发生变更等等情况发生。这些情况下,通过“卡表”(Card Table)“ 写屏障”(Write Barrier)解决,写屏障会在一个对象的引用发生变化时,将该对象所在的页在卡表中对应的表项标记为 dirty。
  3. 并发预清理(concurrent-preclean):

    这一步是可选的,可以通过参数 CMSPrecleaningEnabled 参数可以启用或关闭该阶段,默认是开启的。上文已经介绍到,在并发标记阶段,由于引用的变更,可能会产生一些 dirty page,这一阶段的主要工作就就是处理这些脏页,虽然在后面的重新标记阶段也拥有处理脏页的逻辑,但重新标记阶段会 Stop The World,所以这一阶段的核心仍然是让停顿时间尽量缩短。

  4. 可中断预清理(concurrent-abortable-preclean):

    这一阶段的主要工作是处理新生代指向老年代的新引用,从而让老年代的一些未被标记的对象成为活跃对象。

    同样,在重新标记阶段也会处理这样的情况,这一阶段仍然是为了缩短停顿时间而进行的。

    CMS 对于该阶段有以下 4 个参数:

    -XX:CMSScheduleRemarkEdenSizeThreshold -- 当 eden 区空间超过该值时才执行该阶段,默认为 2M
    -XX:CMSScheduleRemarkEdenPenetration -- 当 eden 空间使用率大于该值时,中断该阶段的执行,默认为 50%
    -XX:CMSMaxAbortablePrecleanTime -- 该阶段的最长执行时间,默认为 5s
    -XX:CMSScavengeBeforeRemark -- 强制在该阶段进行一次 minorGC

    显然,在这一阶段中要识别新生代对象对老年代对象的新引用,那么就必须扫描整个新生代,这显然是一项很耗时的操作,但由于新生代的对象大多是朝生夕死的,所以如果在一次 minorGC 之后紧接着进行一次预清理,新生代中需要扫描的对象就会所剩无几了。CMS 通过 CMSScavengeBeforeRemark 参数强制在可中断的并发预清理阶段执行一次 minorGC,虽然 minorGC 也会让用户线程短暂停顿,但这样可以缩短下一阶段的停顿时间,整体上还是利大于弊的。

  5. 重新标记(Final Remark):

    在上述的并发过程中,用户线程始终在执行,因此随时可能会产生引用变更,比如:
        1.老年代未标记对象被新生代对象引用 
        2.新生代对象晋升到老年代 
        3.已标记对象增加了对未标记对象的引用 
        4.已标记对象的引用被删除  
    这些情况下,很有可能造成标记数据的不准确,如果直接进行清理,就有可能有误清理的情况发生,因此,jvm 需要再一次 Stop The World 来进行重新标记,从而保证在真正的清理前,标记的准确性。
    重新标记阶段,jvm 主要进行以下三个工作:
        1. 遍历新生代对象,重新标记被引用的老年代对象 
        2. 从 GC Roots 出发,重新标记被引用的老年代对象
        3.遍历卡表,对脏页内的老年代对象进行重新标记
    但由于有了前面三个步骤的反复标记过程,重新标记阶段的工作量已经被大大降低,停顿时间当然也因此大大减少。

  6. 并发清除(concurrent-sweep):
    将以上过程没有被标记为可达的对象进行清除,此阶段是与应用程序并发执行的,是不需要 Stop The World 的;

  7. 并发重置(concurrent-reset):
    完成了整个 CMS 的标记-清除工作后,需要将 CMS 算法的内部数据进行重置,从而让下一次 GC 顺利开始。

日志解析参考:JVM 之 ParNew 和 CMS 日志分析_mb5fe94d44e2c3a的技术博客_51CTO博客

  1. 参数:-XX:+UseConcMarkSweepGC开启CMS垃圾收集器
  2. 特性:Cms收集器是一种以获取最短回收停顿时间为目标的收集器。
  3. 应用场景:目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
  4. 优势:并发收集、低停顿。应用程序只在Minor GC以及后台线程扫描老年代时发生极其短暂的停顿。
  5. 缺点:
  • CMS收集器对CPU资源非常敏感

       其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。
       CMS默认启动的回收线程数是(CPU数量+3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集线 程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个(譬如2个)时,CMS对用户程序的影响就可能变得很大。

       如果CMS的后台垃圾收集线程无法获得完成他们任务所需的CPU资源,或者如果堆变得过度碎片化以至于无法找到连续空间分配对象,CMS就蜕变到Serial收集器的行为:暂停所有应用线程,使用单线程回收,整理老年代空间。

  • CMS收集器无法处理浮动垃圾

CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。

由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”。
      也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。

-XX:CMSInitiatingOccupancyFraction=75 表示只有在Old区占了75%的内存时才满足触发CMS的条件,CMS有自己的优化算法,没有十足把握不需要自己配置
-XX:+UseCMSInitiatingOccupancyOnly  指定用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction参数的值),如果不指定,JVM仅在第一次使用设定值,后续则会根据运行时采集的数据做自动调整,如果指定了该参数,那么每次JVM都会在到达规定设定值时才进行GC。

  • CMS收集器会产生大量空间碎片

     CMS是一款基于“标记—清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。可以通过下面参数解决内存碎片问题

-XX:+UseCMSCompactAtFullCollection 默认开启,垃圾回收之后是否进行垃圾压缩整理(MSC算法压缩堆内存)
-XX:CMSFullGCsBeforeCompaction=0  几次full gc之后进行压缩整理,0-表示每次

五、G1收集器(讲透G1GC收集器原理


 


 

  1. 特性:G1收集器也是一种并发收集器。设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
  2. 应用场景:目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
  3. 优势:G1从整体来看是基于“标记—整理”算法实现的收集器。G1的设计理念使得它比CMS更不容易遭遇Full GC。

六、ZGC收集器(讲透ZGC收集器原理

七、调优参数

常用参数:
-Xms6g   堆初始内存
-Xmx6g   堆最大内存
-Xmn2g   设置年轻代大小为2G
-Xss128k 设置每个线程的堆栈大小128k,各操作系统默认值不同,大部分默认值:1M
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值,默认8:表示Eden区与Survivor区的大小比值8:1,单个Survivor=1/10的年轻代大小
-XX:MetaspaceSize=256m   元空间最小扩容内存,超过这个进行full gc扩容
-XX:MaxMetaspaceSize   元空间最大可分配内存   
-XX:NewRatio=5  年轻代和老年代比例,5:年轻代和老年代比例1:5,1/6内存用户年轻代,5/6用于老年代
-XX:+PrintTenuringDistribution  打印年轻代垃圾回收前,各年龄内存大小分布
-XX:MaxTenuringThreshold=6  年轻代晋升老年代age阀值,默认:15,每次Minor GC年龄+1,超过阀值晋升到老年代,但是收集器也会根据内存大小分布进行动态阀值大小优化,累计age内存>survivor的空间*50%,XX:MaxTenuringThreshold=当前age
  
-XX:PretenureSizeThreshold=8000000 大对象大小,超过这个大小直接进入老年代,默认0,经过MaxTenuringThreshold次Minor GC之后进入老年代
-XX:+PrintGC  打印GC日志
-XX:+PrintGCDateStamps  打印GC日期
-XX:+PrintGCDetails  打印GC明细
-XX:+PrintGCTimeStamps  打印GC日期时间

八、总结

  Serial收集器:最适用于应用程序的内存使用少于100MB的场景。

  Throughput收集器:如果平均响应时间比最大响应时间更重要(譬如90%的响应时间),采用Throghput收集器通常就能满足。如果目标是要尽可能的缩短响应时间,那么选择使用Concurrent收集器更合适。Concurrent收集器的前提条件是CPU足够强劲

  Concurrent收集器:一般情况下,堆空间小于4GB时,CMS收集器的性能比G1收集器好。使用大型堆或者巨型堆时,由于G1收集器可以分割工作,通常它比CMS收集器表现更好。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值