有同学反馈收到应用RT的报警,其中的流量都来自于网关集群中的一台机器。因为负责网关,就上去看了下并进行排查。整体是一个比较明显的oom,这里只是记录下排查过程,老司机可以略过了。
常规步骤,使用top 和jstat -gcutil 能直观的看到在拼命full gc。推测是出现了oom。
一开始是用 jmap -histo:live [pid] >a.log 导出当前内存对象,为什么没有直接用jmap -dump:live 也是想偷懒,因为-dump 生成的文件太大,我们的服务器又跑在k8s上面,要拿回本地需要通过ftp 中转,想着能省就省。结果发现这个给后面埋了个大坑。
基于上面的结果,一度怀疑是sentinel 引起的问题。特别是对比了正常的机器上的内存分布
在上面浪费了大量时间,后面回过头看其实ConcurrentHashMap 占比这么高说明是缓存管理出现了问题。
老老实实用 jmap -dump:live,format=b,file=xxx.xxx [pid] 打印出详细的内存堆栈,拿到本地后用 IBM HeapAnalyzer(比较轻量) 或者MAT(大&慢,但是好用) 打开分析。
这里是先用了IBM HeapAnalyzer 进行分析。
从上面比较直观看到出现oom的类。这里只是看到单个的类比较大,从源码上看:
会发现有块缓存没有设置长度和失效时间,这个很可能是导致oom的原因。
过了个周末,周一到公司之后同事反馈又出现了oom,还以为没解决上面的问题。于是又来了一遍,把内存dump 下来分析。
可以看到这次出现的类跟之前已经不一样了。这个时候发现 IBM HeapAnalyzer 的一个弱项,他不能直接显示字段名,只能显示类名,这个跟另一个同事使用的在线工具:https://heaphero.io/heap-index.jsp 有明显区别。但是在线工具打开dump 文件非常慢。这个时候就想着用MAT 打开看看。
下载MAT 打开官网:https://eclipse.dev/mat/downloads.php ,这个时候浏览器提示非安全的,又去找了下解决方法。
下载安装好之后,打开提示需要jdk1.17 ,我本地是1.11 。又去下了jdk1.17(https://www.oracle.com/java/technologies/downloads/#jdk17-mac),因为不想破坏本地jdk环境,所以需要配置MAT
增加2行,配置下载到本地的jdk路径
-vm
/Users/ykdsg/opensource/jdk-17.0.8.jdk/Contents/Home/bin/java
然后编辑 MemoryAnalyzer.ini 文件
新增配置
顺便改下jvm的内存,因为dump 文件通常比较大,太小的内存很容易报错。
调整完之后就可以顺利打开了。
可以比较直观的看到字段名。
可以推测是Map的value 类型是List,导致数据重复的增加。
在出现问题的机器上用arthas的getstatic 来看下内部情况:
先用arthas 连上jvm:
java -jar /alidata/deploy/arthas-boot.jar 45
再用getstatic 查看静态字段,-x 2 表示对象下钻的层级
getstatic com.yangt.hop.biz.util.DubboServiceCacheUtils serviceNameKeyMap -x 2
确实出现很多重复的数据,同一个key里面的值太多了,都打不下了:
找到问题之后的解决方案就比较简单了,将List类型改为Set 就能解决这个问题。fix 之后再来看下这个字段的情况:
可以看到每个key 对应的数据基本就1条。
如果要分析oom 还是老老实实用jmap -dump:live 以及配合MAT,大有大的用处。如果dump 的文件太大,本地内存吃紧的话可以考虑在服务器上建立索引,这个有看到过介绍但没试过。
arthas 是个好工具,对线上问题简直居家旅行必备。
对于缓存:如果是自己管理,并没有通过guava 之类的框架,还是要小心谨慎,并且尽量避免用list等非去重的结构。