以下例子出自其他人案例,拿过来是为了学习排查问题思路,与解决思路。
运单系统每隔一段时间,内存使用和CPU报警超出阈值85%,通过监控平台可以看到非堆内存持 续增加不回收
。导致频繁FullGC,引起CPU100%
??从监控上看非堆内存永久代(JDK8 HotSpot JVM将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace))一直增加且无法回收。这部分内存主要用于存储类的信息、方法数据、方法代码等,正常情况下元空间不会占用很大内存,所以对于动态生成类的情况 比较容易出现永久代的内存溢出。开始怀疑应该是使用反射、代理等技术生成了大量的类加载到元 空间无法回收。
各列含义: jstat (oracle.com)
??可以看到发生了FullGC, MU 和 CCSU 都很大,FullGC并没有回收掉,再次确认了元空间内存溢出的可能。
在JAVA_OPION 中添加 -verbose:class 打印类加载信息。重启后观察日志输出
??发现以上输出信息,并且一直不停的Loaded。 打印出了相关的业务类.BusinessMessageBody、BusinessMessage ,看样子是使用Jaxb 序列化成XML时产生的问题。在项目中搜索相关代码。
总之,不是自己的案例,s省略中间的源码分析过程…,直接给结论:
因为框架这一行,会重复执行类的定义,一定重复定义,重复定义,在jvm内部会有一次约束检查出LinkageError,但临时创建的结构,等待GC去回收。
??那么回过来想,为什么 injectors 被回收后,对应的Class实例未被回收卸载掉呢? 此现象产生的环境差异是因为升级了JDK版本,由JDK7升级到JDK8, 那么JDK8对垃圾回收做了哪些改变,是否这些改变导致了此问题的产生。带着这个疑问检索 google,得到了答案。
??在JDK 7中,对Permgen中对象的回收会进行存活性检查,因此重复定义时产生的数据会在GC时被清理。然而在JDK 8中,Metaspace的回收只依赖classloader的存活,当classloader还活着时,它所产生的对象无论存活与否都不会被回收,由此引发了OOM。
从生产环境dump出来的内存文件分析后验证了这一点:
参见 :
假笨说-谨防JDK8重复类定义造成的内存泄漏
JVM源码分析之JDK8下的僵尸(无法回收)类加载器
JAXB导致的Metaspace OOM问题分析
??JAXB-impl 2.2.11 版本 inject 版本种增加了一段 findLoadedClass`` 逻辑,按名称查找是否已有加载的类,避免重复定义加载。
??由于条件问题,一个查询语句把一个表里所有的记录都查询出来了,数据量很大,把内存打爆,
造成现场验收卡,卡,卡…
快速定位报表里的泄露嫌疑犯,点击进去,如图,发现这两个嫌疑犯占用了
49.90%+44.45%=94,35%的内存,还让其他功能咋用呢??
点击第一个嫌疑犯的堆栈信息链接,仔细看就能发现该嫌疑犯是如何行凶的,如下图:
总结:内存分析工具有很多,使用MAT是其中的一种方法。另外,除了手动导出JVM内存信息外,还可以通过设置JVM参数,在JVM发生内存泄露的时候自动导出文件。
过程略,直接给结论:
??本段递归原思想以ids(ArrayList)为容器,循环添加每个子节点包含的全部分组id,并加上自身节点,进行返回。
??但是,由于ids为引用传递,每次addAll操作都会将容器中已有的ids重复添加到容器中,造成每次添加容器中的数据都会发生倍增。
??子分组数小时,不会发生异常现象。ArrayList 的每次扩容包括分配1.5倍的新数组空间,和老数组历史数据的拷贝。
??当子分组数超过27个时,数组长度达到了134217726,当长度超过Integer.MAX_VALUE-8或系统无法给ArrayList分配足够长的连续内存空间时,就会抛OOM异常。
??ArrayList的频繁扩容与拷贝操作,也导致了在执行递归方法开始到抛出OOM异常这段过程中,Cpu一直处于100%的状态,无法处理其它请求。
??目前JVM启动日志中都配置了 内存打满前自动dump,前往对应的机器dump日志进行分析,使用工具为jprofiler,经过分析 发现byte类型占用了435MB的数据,并且调用来源为ibatis.executor.resultset.ResultWrapper,经过jprofiler分析,怀疑有全表查询语句 返回了大对象
导致的内存溢出。
问题sql:
关键数据:
JVM堆栈报告中:大对象:
大对象中的占用内存最大的属性:
未来得及释放的XML解析数据: 该数据已经导出,在后面:
线程调用的HTTP响应方法:
用户上传文件:
??在查看JVM内存堆栈信息分析报告的,Orverview的大对象图表中,说名称为"http-1601-18"的线程事例时占用内存超过2G;继而查看该线程实例包含属性实例明细列表;
??发现线程中占用内存最大的实例类为:
com.sun.org.apache.xerces.internal.dom.DeferredDocumentImpl ,占用内存 2G 左右;初步可以确定此实例的创建造成了JVM内存溢出;
??而该实例是POI工具包用于解析xlsx格式文件中的XML文件用的,也就是说是因为该线程执行过程中需要有xlsx文件解析逻辑。
进而查询该线程的调用HTTP业务接口,是:
com…master.goods.controller.GoodsNewController#importBatGoods ;
结合代码,获知此方法是用于批量导入事业部商品数据的,其大致逻辑是:
??结合的代码逻辑,堆栈信息分析,初步结论为某一用户在执行改操作时,导入了过大文件,导致此次的JVM 内存溢出事故;
然后查询四台主机操作日志:果然发现同一个用户在每次告警前的几分钟,都在商家工作台执行了商品导入操作;
??因而得出本次JVM内存溢出的最终原因为:系统并未对导入文件大小做限制或限制太宽松; 从而导致用户导入较大的文件时在解析过程中出现 JVM内存溢出问题;
??因而得出本次JVM内存溢出的最终原因为:系统并未对导入文件大小做限制或限制太宽松; 从而导致用户导入较大的文件时在解析过程中出现 JVM内存溢出问题;
??部分请求出现502的情况。经查看发现5台服务器中的一台宕机,Tomcat进程已经不存在了。
??对该服务器及负载下的其他服务器的运行状态进行观察发现:该服务器的jvm已经没有数据,其他服务器jvm内存均接近100%,看来其他服务器也岌岌可危啊。
??查看了下宕机的那台服务器日志,关键信息如下:
??为了查看该日志文件,和jdos同事,申请开通了 该服务器 和 另外一台濒临崩溃的服务器的 终端连接信息;
??该文件信息基本上全是当时崩溃是的内存的一些大小参数(忘记截图),参考意义不大;
??紧接着为了查看内存中到底是什么对象导致了内存的崩溃,由于jvm崩溃了,获取不到现在的一些信息了,于是连接到另外一台“濒危”的服务器上,去查看jvm内存的一些状况,具体情况如下:
?其实这个时候离成功仅有一步之遥了。
正在兴奋的解决中,杨xx同学也参与进来建议用sudo -u admin ./jstack -l -F 139 > stack.txt 打印堆栈信息,如图:
查到有7000多个线程池,可以定位某个地方在疯狂创建线程池,肯定了第一步的判断。
然后全文搜 Executors,发现之前的同事有两处创建线程池,并且是每次该请求接口基本上都会创建一个固定4个大小的线程池!原因终于找到了;此处列举一处:
于是,将宕掉的机子重启,然后频繁的去请求该接口,复现了异常情况,由此可以肯定了判断。此处修改为启动的时候创建一个线程池,请求的时候创建一个Callable实现即可。
续:补上此次服务器的jvm参数,以及dump文件大小
使用命令 sudo -u admin ./jmap -dump:format=b,file=a.hprof 139。
jdos两台机器JVM突然挂掉(11.26.78.xx、10.190.183.xx),查看MDC监控,发现jvm挂掉的时间点 机器内存占用很高(99%);
内存很平稳,每次yong gc,都能降下来;至JVM挂掉之前,未触发过full gc;CPU和线程数也都很正常。
?Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉。 因此,你发现java进程突然没了,首先要怀疑是不是被linux的OOM killer给干掉了!
OK,顺着这个思路,确定到底是不是被oom killer干掉的。
查看系统报错日志: /var/log/messages,发现此文件是空的(后来咨询jdos运维同事得知,docker实例写此日志被禁用)
查看内核日志:
dmesg输出信息有调用oom-killer,但是从内存来看貌似是宿主机的(totalvm:22120752kB),时间点无法确定;
联系运维查询:
可以确定JVM是被操作系统oom killer干掉了。
问题:docker规格4c4g,为何java进程会占用5.2g?
jdk1.8及之前,JVM是感知不到容器的存在的,所以会使用宿主机的信息来计算,docker -m参数一般用来限制应用内存大小,跟镜像版本也有很大关系,有的版本限制的差不多相当于物理内存的 百分之50。
模拟:OOMKilled
上面两个案例说明一个问题:遇见jvm异常退出,先找dump文件,dump如果没有,找hs_err_pid.log日志。如果还没有,翻内核日志。
jvm建议配置:
以8G内存为例:
1-垃圾回收器的参数
2-元空间需要配?512,350
3-堆内存最大最小?4G
4-栈大小?512k
5-直接物理内存?
6-GC日志
不建议配置的: 各个区域的比例,默认值。