记一次OOM引发的线上事故

2023-10-25 11:45:17 浏览数 (1)

背景

最近线上服务平均两周就会出现重启的现象,同时SRE的同事发现重启前服务占用内存非常高,重启后内存下降,可见该服务是逐步耗尽系统内存。给力的SRE同事最后发现是服务所用的框架导致的内存被撑爆了。本文在本地模拟在这次线上问题,并记录这次问题的排查过程

本地模拟

首先由于线上服务间隔两周才出现重启的情况,为了在本地模拟线上Metaspace OOM的情况, 采用JMeter对本地服务进行压测。本地的操作系统是Mac OS。使用top 命令查看压测之前的程序的内存占用情况,如下图所示

可以看到程序整体占用2495M。然后用JMeter对服务进行压测。在压测的过程中在终端使用反复使用jcmd [PID] GC.heap_info命令, 重点观察Metaspace 区域数据变化

可以看到Metaspace中committed在飙升

排查与解决

使用jmap -dump:format=b,file=[filename].bin [PID]命令分别在压测刚开始和结束之前打印堆快照。然后使用MAT工具对快照进行分析。下面第一张图片一开始的快照, 第二张是压测之后的快照。

对比上面两张图可以明显看到,压测一段时间后Class的数量与Class loader的数量有了显著的上升。我们知道Java 8使用Metaspace来存贮类的元数据以及类加载器,Metaspace占用的是本地堆内存(native heap),所以Metaspace的增长导致了本地内存“撑爆”了。

可以通过-XX:MaxMetaspaceSize来设置触发Metaspace回收的阈值, 保证Metaspace不会太大,超过阈值就FullGC。但是这治标不治本,为什么会有这么多类加载器呢?

TranletClassLoader并不是业务层自定义的ClassLoader, 深入到框架层面的代码发现有如下代码。

每次请求都会调用template.newTransformer(), 该方法每次都会生成一个TransletClassLoader 去加载类TemplatesImpl,慢慢导致Metaspace不断增长。询问同事知道,这个API是框架层面暴露给监控部门调用,获取服务的健康状态, 请求量不是很大,所以才会有两个礼拜内存才满的现象。

找到问题的原因之后,目前先给线上服务加上-XX:MaxMetaspaceSize参数,框架部门的同事开始着手处理代码问题。

总结

Java 8开始彻底移除PermGen, 引入Metaspace来解决java.lang.OutOfMemoryError: PermGen。但是不代表对Metaspace可以不管不问了,使用不当Metaspace会导致本地内存溢出的情况。也有JVM大神就觉得Metaspace取代perm的好处不是那么明显,有点费力不讨好的感觉。

0 人点赞