记一次服务Full GC背后的内存泄漏问题,真是匪夷所思

1. 告警最近所负责的服务略频繁地收到4xx告警
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
2. 问题定位1、查业务日志 , 没发现相关错误的日志2、查nginx access log , 发现返回的状态码都是499 , 从request_uri查了一遍发现不是集中在某一个请求上 , 说明应该不是某个接口的问题了 , 有可能进程层面问题了 。
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
通过对upstream_addr 分类 , 可以看到问题基本都是集中在 某一台这台机器上
3、网上资料了解到 , 499 是 nginx 扩展的 4xx 错误 , 代表客户端请求还未返回时 , 客户端主动断开连接 。 原因有几种 , 不过大部分原因都说到有可能服务器upstream处理过慢 , 导致用户提前关闭连接 。 那就先往这个方向排查 , 登录机器查看实际的access.log
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
发现upstream response都是10s以上 。 这就证明了上游服务器处理10秒还没有响应 , 因此nginx提前关闭链接 , 返回499
4、为什么进程响应如此慢 , 10秒太不正常了 。 考虑到那段时间就只有一台机器有问题 , 而且是进程层面的问题 , 首先想到的是GC , 于是再次登录到机器上查看gc log 。 发现有Full GC , 时间点和告警的时间也吻合 。惊呆的是 , 这次FullGC耗时长达19.07秒 , 由于我们的服务使用的是jdk8默认的ParallelGC , FullGC期间 , 整个应用Stop The World的 。 这是非常恐怖的一件事
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
由此看来 , 4xx告警的初步原因已经定位到 , 就是FullGC导致的 。
3. FullGC原因排查那么究竟为什么会发生FullGC呢?需要深入分析一下 。 借助服务治理平台的JVM监控观察了几天 。 期间不同机器不同时间也发生了几次FullGC 。 从监控发现 , 基本每台机器隔两天就会发生一次FullGC , 每次FullGC后年老代回收的垃圾不算多 , 使用比例还是挺高的 。
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
为什么年老代空间占用这么多?
继续分析上面那条full gc log , 1、发生full gc时 , 年老代内存已经占用了99.98%了(1048397/1048576) 。 看起来因为年老代满了而触发的FullGC了 。 2、full gc回收了年老代大约302M的垃圾 , 回收后年老代占用70.4%(738282/1048576) 。 这占用率还是比较高的 。
4. 年老代对象分析【记一次服务Full GC背后的内存泄漏问题,真是匪夷所思】1、首先jmap简单打印一下所有对象的信息 。 发现有ClassPathList和ClassClassPath两个类的对象数量高达1000多万 , 并且这两个数量是一样的 。 仿佛嗅到了内存泄漏的味道 。
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
2、只依靠对象统计信息 , 不足以定位问题 , 需要使用完整HeapDump , 通过MAT进一步分析
jmap把完整堆heapDump下来
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
隔一段时间后 , 继续jmap , 这次只取存活对象的dump(实际效果是先执行一次FULL GC)
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
可以看到 , 经过Full GC后 , ClassPathList对象没有被回收 , 数量反而继续增加 。 到这里 , 基本可以确定 , ClassPathList是存在泄漏了 。
5. 年老代ClassPathList分析那么 , ClassPathList究竟被谁引用着 , 导致回收不掉呢?通过MAT的OQL过滤出老生代的ClassPathList对象 , 从对象的关联关系上继续深入分析 。
首先需要知道老生代的地址区间 , 可以使用vjtools
通过vjmap的address命令 , 快速打印各代地址 。
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
可以得知 , oldGen的下界是0x80000000 , 上界是0xc0000000(注意OQL中使用时要把数值前的那串0去掉) 。
执行OQL只查询年老代中的ClassPathList对象:执行OQL只查询年老代中的ClassPathList对象:
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
抽取其中一个对象分析 , 可以发现这个ClassPathList对象被一连串不同ClassPathList对象的next属性引用着 。 看起来是个链表的结构
记一次服务Full GC背后的内存泄漏问题,真是匪夷所思文章插图
再看看GCRoot , 发现是被AppClassLoader也就是我们的应用类加载器引用着 。 除非这个加载器卸载了 , 否则ClassPathList对象是不会被GC掉了 。