❝
大家好呀,我是小羊,如果大家喜欢我的文章的话😁,就关注我一起学习进步吧~
❞
1.前言
最近一个小伙伴向我提了一个问题,说是能不能一起解决一下,问题是,他们的一个微服务最近老是OOM,很奇怪的是这个服务只是一个管理后台的服务,给内部人员使用的,理论上应该并发不高,但是在运行的过程中,发现内存不断升高,最终达到内存的最大限制,然后oom 了。
大概就是下面这样的:

可以看到,随着内存的升高,cpu的占用也不断升高,大概是因为jvm 对象回收不了,全部进入老年代,然后老年代,内存不够导致频繁fullgc。fullgc 是非常消耗cpu 资源的。所以导致了cpu占用飙升。

2.准备
由于我们微服务没有配置内存溢出保存堆快照,所以我们打算使用命令保存堆快照,然后导出来用 mat查看。
使用jmap 命令导出当前堆快照。
jmap -dump:format=b,live,file=aa.hprof 10
10 是 微服务的进程id。
这里介绍一下jvm 命令
-
jps :基础工具 查看JAVA进程PID。jps 命令用来查看所有 Java 进程,每一行就是一个 Java 进程信息。jps 仅查找当前用户的 Java 进程,而不是当前系统中的所有进程,要显示其他用户的还只能用 ps 命令。 -
jinfo:命令可以用来查看 Java 进程运行的 JVM 参数 jinfo {PID} 如果不加JVM参数的话,默认查看JVM中所有被修改过的值。 -
jstat:主要是对java应用程序的资源和性能进行实时的命令行监控,包括了对heap size和垃圾回收状况的监控 查看JVM中相关性能的信息。jstat(Java Virtual Machine Statistics Monitoring Tool)是从JDK1.5自带的一个轻量级小工具。它位于java/bin目录下,主要利用JVM内建的指令对Java虚拟机的资源和性能进行实时的监控。 -
jstack:查看某个Java进程内的线程堆栈信息 查看JAVA进程当中,线程内容。 -
Jmap jmap是JDK自带的工具软件,主要用于打印指定Java进程(或核心文件、远程调试服务器)的共享对象内存映射或堆内存细节。可以使用jmap生成Heap Dump。在Java命令Java Dump和Java命令:Jstack中分别有关于Java Dump以及线程 Dump的介绍。

因为是在容器中的,所以先把堆快照从容器中复制出来。
docker cp 4334a6dfae55:/opt/webapp/aa.hprof /root
4334a6dfae55 是容器id
/opt/webapp/aa.hprof 是容器内堆快照的路径
/root 是linux 虚拟机的路径
3.分析
复制出来后,导入到mat(memory analyzer tool)
mat官网:http://www.eclipse.org/mat/

有几个比较重要的地方
Histogram: 直方图,主要用来查看class的数据和占比
Leak Suspects:漏洞报告,可以大概定位到问题和所在位置
Dominator Tree: 支配节点数:可以看到对象的数据和占比
thread overview: 查看堆快照中线程情况,可用于定位问题代码

可以看到,mat 认为是elesticSearch 出了问题,并给出了类的路径:org.frameworkset.elasticsearch.template.ESUtil,这个是 bboss-elasticsearch-spring-boot-starter 官方包里面的工具类,这个包很多人都有在用,应该是不会有问题的,难道发现了官方的bug?

带着这样的疑惑,我又打开了Histogram 直方图:
可以看到 charp[] 类型的资源占用是非常多的,我们学java都很清楚char类型是基本类型,String 底层就是char,所以我们还需要进一步深入。
右键char列 -> merge shortest paths to gc roots -> with all references 找到这个类型的所有引用。
继续看Dominator Tree: 支配节点数,看一下对象的占用情况,

点开之后,看到有一个String 对象所占用的内存非常大,而且也是和 EsUtil 这个类有关联的,看起来有点头绪了,如果找到这个对象是在哪里使用的,就可以定位到问题了。
我把这个String 这个对象复制下来看一下它大概有多长复制到 vs code 之后,发现居然有20多mb, 11w 行。
问了一下是不是bug, 得到的回复不是bug,确实需要这么查。。

4.debug
大致定位到了问题,还需要debug一下看看具体位置,看看是不是而是使用不当导致的。看了一下数据类型,可能是 elesticSearch 的查询条件,debugger 一下查询 elesticSearch 接口的代码,看一下这个是在哪里使用的;

点进来之后,发现有点像是 es 把请求的查询条件缓存下来了,缓存的内容正好就是 mat 上面的那个大的String对象。




看源码才知道 es 创建了一个缓存对象,默认最大的大小是 20002 也就是 4000,用于存储查询条件,如果超出了最大值,就会触发清空缓存逻辑,把查询条件清空。由于查询条件太大,有的有20MB, 204000 就有 大概78g 的对象需要存储。。。
5.解决
这个缓存大小也是可以配置的,于是我们就改小了一些,改成了10。


然后测试部署上线,运行一段时候没有出现oom,问题解决,完结撒花。

6.总结
说实话这个问题还挺少见的,也没有什么规律,es官方估计也没有想到查询条件会这么长,导致内存都装不下了。不过还是建议大家在做查询的时候,如果传参有list 之类的,还是要限制一下大小,如果返回的是list,也要限制一下大小,或者分页。遇到内存溢出,学会使用jvm 分析工具和方法,就可以很快定位到问题啦,奥力给。
今天的分享就先到这里啦。
喜欢我的话,可以给我点个赞呀。

原文始发于微信公众号(小羊架构):一次微服务内存溢出引起的事故
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/171738.html