现象
spring boot项目jvm启动配置-Xms4g -Xmx4g,然而很不幸的是程序所占的内存越来越高,都达到了12个多G,只能临时重启服务
常用命令
jstat -class PID
jstat -compiler PID
jstat -gc PID
jstat -gccapacity PID
jstat -gcutil PID
查看堆比例jstat -gccause PID
jstat -gcnew PID
jstat -gcnewcapacity PID
jstat -gcold PID
jstat -gcoldcapcacity PID
jstat -printcompilation PID
jmap -histo PID
查看类的实例jmap -heap PID
查看堆栈信息jmap -dump:live,format=b,file=/tmp/m.hprof PID
保存内存的堆栈为文件jhat -J-Xmx2048m -port 5000 /tmp/m.hprof
在线查看堆文件的类,速度比较慢jcmd PID GC.run
强制gc
排查一:开发环境和测试环境调试
用jdk自带的jvisualvm.exe,查看最占空间的类和实例最多的类,找到其最近的内存释放点一般就是内存泄露对象,也可以用jmap查看jvm进程实例最多的类
- 本机启动程序,postman或者jmeter调用程序接口,可以直接直接用jvisualvm查看到堆栈信息
- 远程调试,在测试环境启用jmxremote,-Dcom.sun.management.jmxremote.port=10096 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
但是堆栈信息只能先保存在测试服务器上,下载到本地后再用jvisualvm.exe打开
排查二:保存生成环境的内存进行分析
先用jmap命令将程序的内存数据保存下来,jmap -dump:live,format=b,file=m.hprof PID,其中PID是程序的进程号
/data/server/jdk/jdk1.8.0_171/bin/jmap -dump:live,format=b,file=/tmp/m.hprof 3478
- 将/tmp/m.hprof堆栈文件下载到本地电脑,用jvisualvm.exe打开分析,生产服务器外网太小,下载好慢
- 用mat分析,我是在一台空闲一点的生成服务器上使用,阿里云内网传输文件速度特别快,将下载的mat直接解压缩就可以使用了,因为文件太大内存不足所以需要修改 MemoryAnalyzer.ini中的参数-Xmx4096M
cd /data/tools/mat
./ParseHeapDump.sh /tmp/m.hprof org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
程序执行完以后会在/tmp下面生成一堆文件,其中 m_System_Overview.zip ,m_Top_Components.zip,m_Leak_Suspects.zip三个压缩文件就是报告了,可以看到程序的运行概况,最大的对象,和推测泄露点
从我的内存泄露报告中可以看到Global对象groovy.lang.MetaClassImpl对象是两个主要的内存泄露点
解决
1)复查程序发现将groovy脚本封装到了一个ThreadLocal中,在一次请求中可以被重复利用,该ThreadLocal包括了Global对象,在函数处理结束后释放掉ThreadLocal对象,主要内存泄漏点得到修复
2)groovy.lang.MetaClassImpl泄露解决,先升级groovy-all的版本
发现很多的实例其实已经没有引用了,但是内存得不到释放,当系统内存不足的时候才会被释放掉
最终解决:
- spring-boot升级到2.1.1.RELEASE
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<type>pom</type>
</dependency>
- 废用tomcat容器,改用undertow当做容器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
3)java启动参数的顺序调整
生产服务器上有些spring-boot工程的-Xmx生效,有些没有生效,并且所有的jar包jvm参数全部放到了jar包的后面
经过测试如果在本地电脑把参数放在jar包的后面是无效的
正确的写法: java -jar -Xmx2048m -Xms1024m test.jar --spring.profiles.active=prod