jvm的内存溢出问题排查
- [1. 背景](#1. 背景)
- [2. 根据不能访问的同学的信息找到 报错日志](#2. 根据不能访问的同学的信息找到 报错日志)
- [3. 分析现象与报错日志](#3. 分析现象与报错日志)
- [4. 从监控查看jvm运行情况](#4. 从监控查看jvm运行情况)
- [5. 根据监控观察到得情况初步分析。](#5. 根据监控观察到得情况初步分析。)
- [6. 查看GC日志,metaSpace概要使用情况。](#6. 查看GC日志,metaSpace概要使用情况。)
- [7. 使用arthas查看类加载情况](#7. 使用arthas查看类加载情况)
- [7. 使用arthas堆中所有的类情况](#7. 使用arthas堆中所有的类情况)
- [8. 使用heap dump,和 MAT(memory analyser tool)间接推断反射类被哪些业务代码加载,是否有问题。](#8. 使用heap dump,和 MAT(memory analyser tool)间接推断反射类被哪些业务代码加载,是否有问题。)
-
- [8. 1 打印heap dump并下载到本地](#8. 1 打印heap dump并下载到本地)
- [8. 2 安装配置MAT,并适配jdk](#8. 2 安装配置MAT,并适配jdk)
- [8. 3 逐个分析每个反射生成的类的被引用情况](#8. 3 逐个分析每个反射生成的类的被引用情况)
- [8. 4 分析与结论](#8. 4 分析与结论)
1. 背景
随着项目的发展,应用开始报错。
- 有时响应很慢
- 无端的报错且同样的接口,有的同学,可以访问,有些同学不能访问
2. 根据不能访问的同学的信息找到 报错日志
目前主要有两种
报错a:为
java
Caused by: java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError:Metaspace
报错b:为
java
org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
3. 分析现象与报错日志
首先从报错日志开始分析
一个很直白,内存溢出,没有metaSpace了
一个比较隐晦:spring框架生成class时,无法生成,可能得原因有:堆空间不足或者metaSpace空间不足,又或者其他原因。
日志指示着,需要查看,metaSpace或者heap空间,需要查看jvm得运行状况。
响应很慢:看过接口,有些机器是正常响应,有些机器则响应得很慢,这不是业务代码得问题,跟机器有关系了。
同样得接口有的同学可以访问有的同学不能访问:其实是两个同学访问得机器不同。而访问报错得日志就是上面主要就是上面得两种之一。
得出结论:需要查看jvm运行情况,尤其是 metaSpace和heap空间
4. 从监控查看jvm运行情况
打开监控,发现
metaSpace空间 占用超高,有些机器98%了,最低得也有70%,metaSpace空间使用有问题。
cpu 有时飙高达到100%,且会持续一段时间,cpu飙高有问题。
fullGC 频繁进行,大于健康频率,而且metaSpace占用越高,fullGC进行就会越频繁, fullGC 有问题。
heap空间,占用率不高,只有30%左右,每个机器都差不多,正常。
5. 根据监控观察到得情况初步分析。
metaSpace是存放类信息得地方, 每次meatSpace满得时候会触发 fullGC。频繁fullGC。 而fullGC可能会执行stop-the-world指令会影响业务响应。观察cpu飙高得时间和fullGC高度重合,这可以理解为 fullGC导致cpu飙高。归根结底因该是 metaSpace使用有问题导致。
6. 查看GC日志,metaSpace概要使用情况。
发现 metaSpace 几乎是单调递增,很难被卸载,且99%以上的空间被NoClass 类型数据所使用。
且metaSpace 占用率一直维持再高位。
需要查看 metaSpace都是哪些类,都有哪些类加载,都是什么代码加载的。
7. 使用arthas查看类加载情况
观察到sun.reflect.DelegatingClassLoader 的加载了大量的类,且类都没有被卸载过。
不像是其他的类加载器,加载了很多,但都卸载了。
powershell
classloader
我可以看到类似如下的
| name | numberOfInstances | lodaderCountTotal |
|---|---|---|
| sun.reflect.DelegatingClassLoader | xxxxxa | xxxxxa |
7. 使用arthas堆中所有的类情况
可以看到很多类都是反射生成的
powershell
sc sun.reflect.Generated*
8. 使用heap dump,和 MAT(memory analyser tool)间接推断反射类被哪些业务代码加载,是否有问题。
8. 1 打印heap dump并下载到本地
powershell
ps -aux | grep java
jmap -dump:format=b,file=heap.hprof <pid>
8. 2 安装配置MAT,并适配jdk
MAT(memory analyser tool) 从eclipse 官网搜索 mat 下载。一般要求jdk至少为17,本地调换升级jdk就可以
如果dump文件很大,需要增加mat的内存占用。
如果解析报错,设置mat的preference 出现异常为 仅报warning,不中断。
8. 3 逐个分析每个反射生成的类的被引用情况
打开 histograme, 过滤出反射类(sun.reflect.Generated*)
右击类名称,list objects -> with incoming references
逐层点开引用,能到看具体的反射类名/方法名称 以及被什么业务类所引用
观察到
主要有:
- mybatis 反射生成DO (大量)
- spring容器加载时反射创建Bean(少量)
- FastJSON序列化与反序列化(有一些)
- spring通过反射强制知道具体类型的情况(controller的方法接收参数/返回参数,BeanUtils.copyProperties)(大量)
8. 4 分析与结论
接口很多,且表越来越多,mybatis和spring反射创建的类型信息会缓存起来,不会丢失,导致metaSpace不够用了。
增大jvm 的 metaSpace空间即可。
powershell
-XX:MetaspaceSize=1g -XX:MaxMetaspaceSize=1g