1.young gc耗时陡增
日志现象是real time远远高于user time
image.png
YGC实际的STW停顿时间包含两部分:GC时间(例如,young GC)和GC记录日志的时间(例如, 调用write()的时间)。user比较低说明YGC动作比较快,real比较高,只能是GC记录日志的耗时比较高了
2.young gc耗时缓慢增长
查看详细的日志发现是StringTable Root扫描导致
StringTable的数据结构是一个HashTable,代码调用String.intern()时,会先去该StringTable中查找是否存在相同的字符串。以此达到缓存的作用。
JVM字符串常量池在JDK8 中是将字符串对象放在堆中(jdk7是放到perm区,但是那样会经常导致永久代内存溢出,所以在jdk8中将它移到了heap区),通过StringTable来维护引用,StringTable 结构是类HashMap的实现,如果数组长度过短(String Table (JDK8)默认长度时60013 ),hash碰撞频繁就会导致链表过长,影响查找效率,耗时过长。
那么为什么YGC需要去扫描StringTable呢,因为jdk8字符串常量池挪到了堆中,YGC的时候需要看看新生代里面的字符串对象是否被StringTable引用了。如果被引用了,相当于了一个元数据,后续可以复用,YGC不应该回。如果StringTable很大,扫描会很耗时。
3.young gc old-gen scanning 耗时高
对于 YGC,在从 GC Roots 开始遍历并标记所有的存活对象时,会放弃追踪处于老年代的对象,由于需要遍历的对象数目减少,能显著提升 GC 的效率。 但这会产生一个问题:如果某个年轻代对象并不能通过 GC Roots 遍历到,而某个老年代对象却引用了该年轻代的对象,那么该如何正确标记到该对象?
为解决这个问题,一个最直观的想法就是遍历整个老年代,找到其中持有年轻代引用的对象,但显然这样做的开销太大,且违背了分代 GC 的设计。因此,垃圾回收器必须能够以较高的效率准确找到并跟踪那些处于老年代且持有年轻代引用的对象,并将这部分对象放到和 GC Roots 同等的位置,这就是 old-gen scanning 阶段的来历。
调整ParGCCardsPerStrideChunk参数
3. full gc频繁
jdk1.8持久代升级为元空间,如果线上服务使用的是jdk1.8,检查jvm参数是否配置了-XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M 参数。如果没有配置,jvm默认会在持久代到了20.8M的时候触发full gc,以后每次扩容还有可能触发full gc。(大家不用担心配置过大,1.8与1.7不一样的是,即使你配置了-XX:MetaspaceSize=512M 也不会给程序分配512M内存,而是用多少分配多少)
4. 高峰期发生cms full gc
流量摘除,服务负载升高,导致服务拒绝。用G1
网友评论