目录

JVM调优学习之旅-(2)ParNew+CMS

ParNew

1
Serial 与 Serial  old  单线程的就先不分析学习了

到底什么时候会尝试触发Minor GC

一般年轻代内部内存比例是8:1:1

当Eden区满了会触发minor gc把活下来的对象放入剩余的s区中。

触发Minor GC之前会如何检查老年代大小,涉及哪几个步骤和条件

  1. 判断老年代剩余空间是否大于新生代所有对象内存空间。
  2. 如果HandlePromotionFailure 这个参数设置后会进入下一步判断,老年代剩余大小是否大于之前minor gc进入老年代的平均大小(担保)
  3. 如果上面判断失败了或者这个参数没设置,会直接进入full gc
  4. 如果参数设置了,条件成立后,会冒险尝试minor gc ,如果年轻代放得下就放年轻代,不然就放老年代,如果都放不下 就会执行full gc ,这次full gc 会连带新生代一起进行垃圾回收,如果还不行就OOM了。

什么时候在Minor GC之前就会提前触发一次Full GC?

当老年代剩余大小小于年轻代大小,并且没有设置HandlePromotionFailure时会触发full gc,如果设置了,老年代剩余大小小于年轻代以往GC大小。

Full GC的算法是什么

标记后清理 然后整理 内存块。

Minor GC过后可能对应哪几种情况

剩余S区能放下就放,不能放下就进入老年代,老年代不能放下就full gc。

哪些情况下Minor GC后的对象会进入老年代

  1. 对象年龄大于15
  2. 对象大小大于PretenureSizeThreshold这个参数设置的单个对象大小
  3. 年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区 域的50%,此时就会把年龄n及以上的对象都放入老年代
  4. 同龄对象大小超过 Survivor某区内存的一半,那就把这个年龄以上的对象送到老年代。

采用parnew+cms垃圾回收器如何只做ygc,零full gc

1
2
和垃圾收集器没有什么关系,不同垃圾收集器,差别只在于性能和吞吐量的区别。并不影响垃圾回收时机。
根据堆中对象生存周期特点,合理分配eden s0 s1 大小,尽量让对象在新生代就被回收,需要注意动态年龄判断、 内存担保1.6后默认开启

永久代

大家现在既然都知道了,Full GC有上述几个触发条件,同时触发Full GC的时候其实会带上针对新生代的Young GC,也会有针对老年 代的Full GC,还会有针对永久代的GC。所以假如存放类信息、常量池的永久代满了之后,就会触发一次Full GC。

CMS老年代垃圾回收器

CMS

https://yakax.oss-cn-hangzhou.aliyuncs.com/blog/jvm/TIM%E6%88%AA%E5%9B%BE20200602143539.png

初始标记

这一阶段会停止一切工作线程,“Stop the World”,但是会很快,仅仅标记GC Roots直接引用的对象。

并发标记

这个阶段会很慢,最耗时,因为是对老年代所有的GC Roots进行直接或间接的跟踪标记,由于是并行的,不影响系统。

重新标记

1
2
3
为了保证重新标记阶段耗时尽可能的变短,再重新标记前增加个并发预清理阶段:
另外为了防止并发预清理阶段等太久都不发生young gc,提供了CMSMaxAbortablePrecleanTime 参数来设置等待多久没有等到young gc就强制remark。默认是5s
但是最终一劳永逸的办法是,添加参数CMSScavengeBeforeRemark,在重新标记前强制YGC

这一阶段会停止一切工作线程,“Stop the World”,但是也是会很快,因为仅仅标记前面两个阶段遗留下来的对象。

并发清理

这个阶段会很慢,因为需要进行对象的清理,但是他也是跟系统程序并发运行的,所以其实也不影响系统程序的执行。但是消耗cpu

浮动垃圾问题

当在并发清理的时候可能会出现minor gc 放入一些对象进入老年代。这些对象是没标记到的,所以清理不了。所以在CMS回收期间会预留一点内存空间出来,当老年代内存占用空间比例大于某个比例时就直接进行full GC,可以通过“-XX:CMSInitiatingOccupancyFraction” 这个设置比例。

JDK 1.6里面默认的值是 92%。 也就是说,老年代占用了92%空间了,就自动进行CMS垃圾回收,预留8%的空间给并发回收期间,系统程序把一些新对象放入老年代 中。

但是,当这8%都不够放入回收期间的对象时,会发生Concurrent Mode Failure此时就会自动用“Serial Old”垃圾回收器替代CMS,就是直接强行把系统程序“Stop the World”,重新进行长时间的GC Roots追 踪,标记出来全部垃圾对象,不允许新的对象产生,然后回收再恢复。 如果还是放不下 就会OOM了。

所以在生产实践中,这个自动触发CMS垃圾回收的比例需要合理优化一下老年代在并发清理期间有多少对象进入老年代,避免“Concurrent Mode Failure”问题。

整理内存碎片阶段

由于标记清理过后会有很多内存碎片,内存碎片放不下新进来的对象,就对导致频繁的full gc。

CMS有一个参数是“-XX:+UseCMSCompactAtFullCollection”,默认就打开了,意思是每次full gc 过后再次进行STW,进行碎片整理,

“-XX:CMSFullGCsBeforeCompaction“ 这个参数是多少次full gc 过后进行整理,默认为0,每次都会进行整理。

CMS为啥老年代的Full GC要比新生代的Minor GC慢很多倍,一般在10倍以上

minor gc年轻代,产生的对象一般GC roots引用都不长,好找,并且是一次性回收。存活对象少,迁移内存很快,然后一次性清理垃圾对象,这个速度就是快

full gc 并发标记阶段要追踪所有GCroots 对象的引用,并发清理的时候标记的对象分散在不同的地方,最后完事了还要把碎片整理在一块。

总结老年代触发GC 的几次时机

  1. 老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开;
  2. 开启空间担保情况下老年代剩余空间小于历次新生代进入老年代对象的平均大小。此时会体检full gc。
  3. 新生代minor gc后存活的大对象放不进 Survivor,会直接进入老年代,而老年代此时如果放不下,会进入full gc。
  4. 在CMS垃圾算法下:-XX:CMSInitiatingOccupancyFraction 这个参数设置的比例超出了,也会直接进入Full gc。

总体应该怎么进行优化

  1. 预估系统需要多少内存,老年代新生代内存比例,结合业务预估对象存活时间。
  2. 根据minor gc多久运行一次,每次有多少对象进入s区,对象需要存活多久。来分配新生代区域。
  3. 每次回收新生代后活下来的对象要小于s区一半,这样合理规避动态年龄判断。
  4. 合理设置到老年代的晋升次数,让常驻对象尽快进入老年代,以免留在新生代占用空间。
  5. 注意空间担保打开。(1.6后默认打开了)
  6. 根据业务预估内存比例与对象存活时间,才能合理优化新生代gc次数,才能尽可能少老年代gc次数。

频繁FGC的可能性

  • 内存分配不合理,导致对象频繁进入老年代,进而引发频繁的Full GC;

  • 存在内存泄漏等问题,就是内存里驻留了大量的大对象塞满了老年代,导致稍微有一些对象进入老年代就会引发Full GC;

  • 永久代里的类太多,触发了Full GC

  • System.gc() 导致