起初没有人在意这场 GC, 直到它影响到了每一天!
前言
本文记录了一次排查 FullGC 导致的 TP99 过高过程, 介绍了一些排查时思路, 线索以及工具的应用, 心愿可能帮忙一些老手在排查问题没有很好的思路时, 提供一些思路, 让小白也能轻松解决 FullGC 问题, 文中理论提到的参数配置不肯定适宜其余业务场景, 在调优本人的我的项目时还是须要理论试验过能力得出最佳参数配置
我也是小白, 如有不合理的中央, 欢送大佬们进行斧正
因为线上服务器, 咱们大部分是没有 SSH 权限的, 没有方法间接执行命令获取容器信息, 所以排查过程中只能借助平台提供的工具, 平台提供的工具还是挺全的, 本文次要用到的工具有: JDOS 容器智能监控,JDOS 过程查问,SGM 容器监控信息,SGM 办法调用查问
以下几个工具简略介绍:
http://sgm-server.jd.com/
http://jagile.jd.com/jdosCD/jdt/apps
JDOS 容器智能监控: 查看容器的 CPU, 内存, 磁盘,IO 等信息
JDOS 过程查问: 查看 Java 过程编号, 执行罕用的 Java 内存过程查看命令
SGM 容器监控信息: 查看 JVM 虚拟机内存变更历史记录
SGM 办法调用查问: 查看某一次要害接口调用的高低依赖, 工夫散布
起因 – 偶然呈现接口超时
一开始偶然会收到报警邮件, 显示有些接口调用工夫比拟长, 抽查了一些接口, 发现大部分都是调用上游 JSF 工夫比拟长, 导致响应比较慢, 这时候就没太在意, 接下来持续察看了几天, 发现一个法则, 大部分邮件都是每天 10 点
排查定位问题
- 首先确认了 10 点这个工夫点有没有定时工作之类的操作, 通过询问确定这个工夫点是仓库出库高峰期, 导致业务量呈现峰值 (调用质变大可能是激发 FullGC 问题, 成为问题暴漏的导火线)
- 第二部就是确认是数据库起因, 还是业务代码, 还是 JSF 上游接口达到极限起因, 到这一步还是未知的, 在这用到了 SGM 的接口调用查问工具, 下图中咱们看到, 这次调用 JSF 也是挺高的 (这个没有太好方法, 除非让上游优化, 所以临时疏忽), 然而还有一个是 logic, 这个就是逻辑解决, 如果没有那个 FullGC 提醒, 就须要去剖析代码的解决是否有问题, 这通过那行红色字体的提醒, 很显然咱们确定了是 FullGC 导致的问题
- 咱们去查看一下容器的 FullGC 状况, 的确发现这个工夫点的 FullGC 特地频繁, 到此曾经把问题范畴定位到就是 FullGC 导致的
FullGC 问题排查
Full GC 触发条件:
到这里咱们须要确定一个问题 :“触发 FullGC 的条件是什么?”,老手能够去博客搜寻,当然最好是能记住这个知识点。留神这不是确定“什么起因导致的 FullGC?”,因为这个问题起因太多了,咱们要一步一步排查。上面是我查到的材料,粘到这里供参考.
- Minor GC 触发条件:当 Eden 区满时,触发 Minor GC。
- 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 时被开释掉了, 他们为什么到了老年代呢? 这时候咱们须要确定的第二个问题是:“什么状况下对象会进入老年代?”查资料后有以下几种状况
- 年龄够了: 躲过 15 次 (默认配置是 15 次) minorGC 之后从新生代进入老年代;
- 大对象: 大对象间接进入老年代。有一个 JVM 参数 ‘-XX:PretenureSizeThreshold’ 设置值为字节数,创立超过该大小的对象间接进入老年代, 如果没有配置这个参数, 这个值如同默认是 1M。
- 动静年龄判断:以后放对象的 Survivor 区,雷同年龄的一批对象(以及小于该年龄)的总内存大于该区的内存的 50%,大于该年龄的其余老对象,就会进入老年代(例如 1,2,3 岁年龄的对象占了 S 区的 50% 以上,就会把大于 3 岁的对象挪动到老年代去。所以尽量让 S 区中的对象,占比尽量少于 50%);
- 剩的总量太多: 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 排查思路参考脑图
作者:京东保险 陈林辉
起源:京东云开发者社区 转载请注明起源