java gc

作者: 7d972d5e05e8 | 来源:发表于2020-05-06 14:11 被阅读0次

    转至:小内存为什么不建议使用CMS

    -XX:+UseParallelGC 这个参数,仅仅是用来指定年轻代的收集器。
    年轻代:Parallel Svavenge
    而老年代暂时没有参数设置,仅仅是根据版本来的。网上说:java7和java8默认都是Parallel Scavenge(新生代)+Parallel Old(老年代)。其实是不准确的,或者不正确的。

    我自己电脑通过jconsole调起java控制台,如下:


    image.png

    可以看到:年轻代是PS Scavenge,老年代是 PS MarkSweep。
    PS Scavenge:就是上面的Parallel Scavenge
    PS MarkSweep:这个PS MarkSweep默认的实现实际上是一层皮,它底下真正做mark-sweep-compact工作的代码是跟分代式GC框架里的serial old(这个collector名字叫做MarkSweepCompact)是共用同一份代码的。也就是说实际上PS MarkSweep与MarkSweepCompact在HotSpot VM里是同一个collector实现,包了两张不同的皮;这个collector是串行的。 也可以简单的认为,它就是Serial old收集器。(其实这个答案也不完全对,只能说在某一个时期内它是对的)

    但是!!!知乎里面有个答案:
    说java官方在JDK7u4和java8系列,-XX:+UseParallelGC 是默认开启了
    -XX:+UseParallelOldGC,且它的老年代也叫PS MarkSweep。但是它的实现是ParallelOldGC老年代收集器。

    确定个老年代收集器,真醉了~

    以下是知乎答案。

    ===================================================
    链接:https://www.zhihu.com/question/56344485
    请参考:JDK-6679764: enable parallel compaction by default
    这个改进使得HotSpot VM在选择使用ParallelGC(-XX:+UseParallelGC 或者是ergonomics自动选择)的时候,会默认开启 -XX:+UseParallelOldGC 。这个变更应该是在JDK7u4开始的JDK7u系列与JDK8系列开始生效。

    对应的patch是:
    jdk8u/jdk8u/hotspot: 24cae3e4cbaa

    --- a/src/share/vm/runtime/arguments.cpp    Mon Jan 30 15:21:57 2012 +0100
    +++ b/src/share/vm/runtime/arguments.cpp    Thu Feb 02 16:05:17 2012 -0800
    @@ -1400,10 +1400,11 @@
    
     void Arguments::set_parallel_gc_flags() {
       assert(UseParallelGC || UseParallelOldGC, "Error");
    -  // If parallel old was requested, automatically enable parallel scavenge.
    -  if (UseParallelOldGC && !UseParallelGC && FLAG_IS_DEFAULT(UseParallelGC)) {
    -    FLAG_SET_DEFAULT(UseParallelGC, true);
    +  // Enable ParallelOld unless it was explicitly disabled (cmd line or rc file).
    +  if (FLAG_IS_DEFAULT(UseParallelOldGC)) {
    +    FLAG_SET_DEFAULT(UseParallelOldGC, true);
       }
    +  FLAG_SET_DEFAULT(UseParallelGC, true);
    
       // If no heap maximum was requested explicitly, use some reasonable fraction
       // of the physical memory, up to a maximum of 1GB.
    
    

    在这个改变之前,即便选择了ParallelGC,默认情况下ParallelOldGC并不会随即开启,而是要自己通过 -XX:+UseParallelOldGC 去选定。

    在GC日志里,如果看到Full GC里有"ParOldGen"就是选择了ParallelOldGC。

    [Full GC [PSYoungGen: 480K->0K(3584K)] [ParOldGen: 4660K->4909K(12288K)] 5141K->
    

    之前曾经有讲过在heap size<=3G的情况下完全不要考虑CMS GC,在heap size>3G的情况下也优先选择ParallelOldGC,而不是CMS GC,只有在暂停时间无法接受的情况下才考虑CMS GC(不过当然,一般来说在heap size>8G后基本上都得选择CMS GC,否则那暂停时间是相当吓人的,除非是完全不在乎响应时间的应用),这其实也是官方的建议(每年JavaOne的GC Tuning基本都会这么讲)。

    分界线

    为什么给了一个这么“武断”的建议呢,不是我对CMS GC有什么不爽,相反CMS GC一直是我很热爱的一种GC实现,之所以建议在<=3G的情况下完全不要考虑CMS GC,主要出于以下几点考虑:
      1、触发比率不好设置 在JDK 1.6的版本中CMS GC的触发比率默认为old使用到92%时,假设3G的heap size,那么意味着旧生代大概就在1.5G–2.5G左右的大小,假设是92%触发,那么意味着这个时候旧生代只剩120M–200M的大小,通常这点大小很有可能是会导致不够装下新生代晋生的对象,因此需要调整触发比率,但由于heap size比较小,这个时候到底设置为多少是挺难设置的,例如我看过heap size只有1.5G,old才800m的情况下,还使用CMS GC的,触发比率还是80%,这种情况下就悲催了,意味着旧生代只要使用到640m就触发CMS GC,只要应用里稍微把一些东西cache了就会造成频繁的CMS GC。 CMS GC是一个大部分时间不暂停应用的GC,就造成了需要给CMS GC留出一定的时间(因为大部分时间不暂停应用,这也意味着整个CMS GC过程的完成时间是会比ParallelOldGC时的一次Full GC长的),以便它在进行回收时内存别分配满了,而heap size本来就小的情况下,留多了嘛容易造成频繁的CMS GC,留少了嘛会造成CMS GC还在进行时内存就不够用了,而在不够用的情况下CMS GC会退化为采用Serial Full GC来完成回收动作,这个时候就慢的离谱了。

    2、抢占CPU CMS GC大部分时间和应用是并发的,所以会抢占应用的CPU,通常在CMS GC较频繁的情况下,可以很明显看到一个CPU会消耗的非常厉害。

    3、YGC速度变慢 由于CMS GC的实现原理,导致对象从新生代晋升到旧生代时,寻找哪里能放下的这个步骤比ParallelOld GC是慢一些的,因此就导致了YGC速度会有一定程度的下降。

    4、碎片问题带来的严重后果 CMS GC最麻烦的问题在于碎片问题,同样是由于实现原理造成的,CMS GC为了确保尽可能少的暂停应用,取消了在回收对象所占的内存空间后Compact的过程,因此就造成了在回收对象后整个old区会形成各种各样的不连续空间,自然也就产生了很多的碎片,碎片会造成什么后果呢,会造成例如明明旧生代还有4G的空余空间,而新生代就算全部是存活的1.5g对象,也还是会出现promotion failed的现象,而在出现这个现象的情况下CMS GC多数会采用Serial Full GC来解决问题。 碎片问题最麻烦的是你完全不知道它什么时候会出现,因此有可能会造成某天高峰期的时候应用突然来了个长暂停,于是就悲催了,对于很多采用了类似心跳来维持长连接或状态的分布式场景而言这都是灾难,这也是Azul的Zing JVM相比而言最大的优势(可实现不暂停的情况下完成Compact,解决碎片问题)。 目前对于这样的现象我们唯一的解决办法都是选择在低峰期主动触发Full GC(执行jmap -histo:live [pid])来避免碎片问题,但这显然是一个很龌蹉的办法(因为同样会对心跳或维持状态的分布式场景造成影响)。

    5、CMS GC的”不稳定“性 如果关注过我在之前的blog记录的碰到的各种Java问题的文章(可在此查看),就会发现碰到过很多各种CMS GC的诡异问题,尽管里面碰到的大部分BUG目前均已在新版本的JVM修复,但谁也不知道是不是还有问题,毕竟CMS GC的实现是非常复杂的(因为要在尽可能降低应用暂停时间的情况下还保持对象引用的扫描不要出问题),而ParallelOldGC的实现相对是更简单很多的,因此稳定性相对高多了。
      而且另外一个不太好的消息是JVM Team的精力都已转向G1GC和其他的一些方面,CMS GC的投入已经很少了(这也正常,毕竟G1GC确实是方向)。

    在大内存的情况下,CMS GC绝对是不二的选择,而且Java在面对内存越来越大的情况下,必须采用这种大部分时候不暂停应用的方式,否则Java以后就非常悲催了,G1GC在CMS GC的基础上,有了很多的进步,尤其是会做部分的Compact,但仍然碎片问题还是存在的,哎…

    Java现在在大内存的情况下还面临的另外两个大挑战:

    分析内存的堆栈太麻烦,例如如果在大内存的情况下出现OOM,那简直就是杯具,想想dump出一个几十G的文件,然后还要分析,这得多长的时间呀,真心希望JDK在这方面能有更好的工具…
    对象结构不够紧凑,导致在内存空间有很高要求的场景Java劣势明显,不过这也是新版本JDK会重点优化的地方。
    至于在cpu cache miss等控制力度上不如C之类的语言,那是更没办法的,相比带来的开发效率提升,也只能认了,毕竟现在多数场景都是工程性质和大规模人员的场景,因此开发效率、可维护性会更重要很多。
      
    推荐几篇相关的文章:

    A Generational Mostly-concurrent GC(CMS GC的理论论文)
    The Pauseless GC Algorithm(可以管窥下Zing是如何实现不暂停compact的)
    Understanding CMS GC log
      为什么ygc变慢就是cms gc碎片问题造成,cms gc场景下有一个问题就是对象从young晋升到old时需要寻找放到哪里去,而如果碎片比较多,就会导致寻找的这个过程耗时会大幅增加,通常来说只有这个因素会造成,当然,另外也可以通过增加-XX:PrintFLSStatiscis=2 –XX:+PrintCMSStatiscis启动参数来看看碎片的状况。

    相关文章

      网友评论

          本文标题:java gc

          本文链接:https://www.haomeiwen.com/subject/jqwightx.html