起初没有人在意这场GC,直到它影响到了每一天!

前言

本文记录了一次排查FullGC导致的TP99过高过程,介绍了一些排查时思路,线索以及工具的应用,心愿可能帮忙一些老手在排查问题没有很好的思路时,提供一些思路,让小白也能轻松解决FullGC问题,文中理论提到的参数配置不肯定适宜其余业务场景,在调优本人的我的项目时还是须要理论试验过能力得出最佳参数配置

我也是小白,如有不合理的中央,欢送大佬们进行斧正

因为线上服务器,咱们大部分是没有SSH权限的,没有方法间接执行命令获取容器信息,所以排查过程中只能借助平台提供的工具,平台提供的工具还是挺全的,本文次要用到的工具有: JDOS容器智能监控,JDOS过程查问,SGM容器监控信息,SGM办法调用查问

以下几个工具简略介绍:

http://sgm-server.jd.com/http://jagile.jd.com/jdosCD/jdt/appsJDOS容器智能监控: 查看容器的CPU,内存,磁盘,IO等信息JDOS过程查问: 查看Java过程编号,执行罕用的Java内存过程查看命令SGM容器监控信息: 查看JVM虚拟机内存变更历史记录SGM办法调用查问: 查看某一次要害接口调用的高低依赖,工夫散布

起因 - 偶然呈现接口超时

一开始偶然会收到报警邮件,显示有些接口调用工夫比拟长,抽查了一些接口,发现大部分都是调用上游JSF工夫比拟长,导致响应比较慢,这时候就没太在意,接下来持续察看了几天,发现一个法则,大部分邮件都是每天10点

排查定位问题

  1. 首先确认了10点这个工夫点有没有定时工作之类的操作,通过询问确定这个工夫点是仓库出库高峰期,导致业务量呈现峰值(调用质变大可能是激发FullGC问题,成为问题暴漏的导火线)
  2. 第二部就是确认是数据库起因,还是业务代码,还是JSF上游接口达到极限起因,到这一步还是未知的,在这用到了SGM的接口调用查问工具,下图中咱们看到,这次调用JSF也是挺高的(这个没有太好方法,除非让上游优化,所以临时疏忽),然而还有一个是logic,这个就是逻辑解决,如果没有那个FullGC提醒,就须要去剖析代码的解决是否有问题,这通过那行红色字体的提醒,很显然咱们确定了是FullGC导致的问题
  3. 咱们去查看一下容器的FullGC状况,的确发现这个工夫点的FullGC特地频繁,到此曾经把问题范畴定位到就是FullGC导致的

FullGC问题排查

Full GC 触发条件:

到这里咱们须要确定一个问题 : “触发FullGC的条件是什么?”,老手能够去博客搜寻,当然最好是能记住这个知识点。留神这不是确定“什么起因导致的FullGC?”,因为这个问题起因太多了,咱们要一步一步排查。 上面是我查到的材料,粘到这里供参考.

  1. Minor GC触发条件:当Eden区满时,触发Minor GC。
  2. Full GC触发条件:
  • (1)调用System.gc()时,零碎倡议执行Full GC,然而不必然执
  • (2)老年代空间有余
  • (3)办法区空间有余
  • (4)通过Minor GC后进入老年代的均匀大小大于老年代的可用内存
  • (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代

这里在代码中并没有找到System.gc()的显示调用,个别咱们也不会调用这个办法,所以咱们间接看第二种状况,到SGM中查看老年代变动,后果发现老年代频繁达到90%,而这个工夫正好能够跟下面GC工夫对上.

对象进入老年代的几种状况

咱们都晓得,老年代的对象应该是存活工夫很长的对象,然而咱们发现这些对象都在FullGC时被开释掉了,他们为什么到了老年代呢? 这时候咱们须要确定的第二个问题是:“什么状况下对象会进入老年代?” 查资料后有以下几种状况

  1. 年龄够了: 躲过15次(默认配置是15次) minorGC 之后从新生代进入老年代;
  2. 大对象: 大对象间接进入老年代。有一个 JVM 参数 '-XX:PretenureSizeThreshold' 设置值为字节数,创立超过该大小的对象间接进入老年代,如果没有配置这个参数,这个值如同默认是1M。
  3. 动静年龄判断:以后放对象的 Survivor 区,雷同年龄的一批对象(以及小于该年龄)的总内存大于该区的内存的50%,大于该年龄的其余老对象,就会进入老年代(例如1,2,3岁年龄的对象占了 S 区的50%以上,就会把大于3岁的对象挪动到老年代去。所以尽量让 S 区中的对象,占比尽量少于 50%);
  4. 剩的总量太多: Eden 区存活对象太多,超过了 Survivor 的大小,就间接把这些对象都转移到老年代去。(JDK1.8 空间担保机制)

首先剖析第一种状况,如果呈现大批量这样的对象,代码中呈现了长时间援用(例如:动态Map只加不删),然而咱们能够看到,这些对象在每次FullGC都被开释掉了,阐明这批对象存活的工夫并不长, 而且代码排查也没发现这种代码,临时排除这种状况(这的代码因为是工具包的代码,所以没有太深纠,这为续集留个伏笔). 第二种状况,大对象,咱们到JDOS下载下来JMap-dump内存快照和JMap-Histo对象统计信息,通过对FullGC钱dump剖析,联合GC前GC后对象统计后果,并没有发现大量的大对象,这个根本也排除

通过JMAT(Eclipse Memory Analysis Tools)导入dump文件进行剖析,内存透露问题个别咱们间接选Leak Suspects即可,mat给出了内存透露的倡议。另外也能够抉择Top Consumers来查看最大对象报告。和线程相干的问题能够抉择thread overview进行剖析。除此之外就是抉择Histogram类概览来本人缓缓剖析,大家能够搜搜mat的相干教程。

接下来就是第三种和第四种状况,这时候咱们须要取查看年老代三块区域的变动,尤其是Survivor区域,下图是过后一个状况,S区大小始终在变动,而且基本一致放弃在50%以上,这时候想到了一个JVM高版本个性,会主动关上UseAdaptiveSizePolicy(动静调整),查资料后发现,好多人反馈这个参数会导致对象跨过S区,间接跑到老年代的状况,咱们看到在调用量继续很高的状况,尽然调整到了17M,这必定会导致包容不下过后存活的对象

UseAdaptiveSizePolicy开关参数-XX:+UseAdaptiveSizePolicy是一个开关参数,当这个参数关上之后,虚构机会依据以后零碎的运行状况收集性能监控信息,动静调整这些参数以提供最合适的进展工夫或最大的吞吐量,这种调节形式称为GC自适应的调节策略(GC Ergonomics)。

定位到UseAdaptiveSizePolicy问题

既然这有问题,咱们尝试敞开一下这个参数看下成果,上面是老年代,S区和FullGC,在敞开前和敞开后的成果,敞开之后S区大多数工夫有短缺的空间,而且,老年代和FullGC图也安稳了很多 敞开AdaptiveSizePolicy的形式

开启:-XX:+UseAdaptiveSizePolicy(JDK1.8 Parallel Scavenge收集器默认)敞开:-XX:-UseAdaptiveSizePolicy

发现新的问题

上图中尽管曾经安稳了很多,然而还是有一点小问题,频繁FullGC尽管没有了,然而一个小时还是会呈现一次FullGC,而且这时候老年代还没有满,这种频率的FullGC,实践上也是不容许的. 咱们回到第一个问题,FullGC触发条件,第三个,咱们连忙看了下永恒代,也就是元空间,如下图,这一看不得了,元空间也在频繁变动,而且达到300M左右时会触发一次FullGC开释掉.

tips: 这里是没有配置元空间的大小的,也没有配置元空间的实践上元空间无限大,不会满,查问材料后解释是,元空间也会依据以后已应用进行动静调整,当达到上次调整值90%后就会FullGC,所以每次FullGC元空间大小在200M到500M不等

元空间内存排查

这时猜想可能是代码中呈现了大量的动静类的申明,想要定位哪些类须要jvm启动参数加上打印类加载和卸载的参数,顺带把GC日志开关也关上

-XX:+TraceClassUnloading -XX:+TraceClassLoading -XX:+PrintGCDetails

关上后查看日志发现一个频繁加载和卸载的类[com.googlecode.aviator.Expression], 经查问材料,这个是aviator 工具的一个规定引擎类,在加载规定时会动静加载一个类,默认不应用缓存,能够关上缓存避免频繁申明新类

批改代码后重新部署,一小时一次的FullGC也没了,如下图

总结

发现的问题: 问题一: AdaptiveSizePolicy导致对象提前进入老年代,老年代增长速度快,导致频繁FullGC 解决形式: 敞开:-XX:-UseAdaptiveSizePolicy

问题二: 元空间一直增长,导致一小时一次FullGC 解决形式: 批改逻辑代码避免频繁加载新类

在排查问题时尽可能先找间接起因,放大排查跨度,不要一步就想晓得根本原因,每个线索都要问个为什么,不失常的景象必定是有起因的.

上面是FullGC排查思路参考脑图

作者:京东保险 陈林辉

起源:京东云开发者社区 转载请注明起源