文章目录
- Spring Boot —— 如何排查内存溢出问题
- 前言
- 场景一
- 思考
- 常用排查命令
- 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 -gcoldcapacity 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
- 如何排查?
- 1.开发环境和测试环境调试
- 2.保存生产环境的内存进行分析
- 如何解决?
- 方法1
- 方法2
- 最终解决
- spring-boot 升级到2.1.1.RELEASE
- 废用tomcat容器,改用undertow当做容器
- java启动参数的顺序调整
Spring Boot —— 如何排查内存溢出问题
前言
总结看到的博客,记录下,有备无患。
场景一
Spring Boot 项目JVM启动配置为 -Xms4g -Xmx4g,程序运行一段时间内存越来越高,最终超过了可承受的内存容量,只能临时重启服务,达到应用有效。
思考
常用排查命令
jstat -class PID
显示Java堆中类的加载、卸载、总数和大小等统计信息。
- Loaded:已加载的类的数量。
- Bytes:已加载的类占用的字节数。
- Unloaded:已卸载的类的数量。
- Bytes:已卸载的类占用的字节数。
- Time:类加载和卸载所花费的时间(毫秒)。
jstat -compiler PID
显示JIT编译器的统计信息,包括编译任务数量、编译耗时等。
- Compiled:已编译方法的数量。
- Failed:编译失败的方法的数量。
- Invalid:无效的方法(不需要编译或已被编译器忽略)的数量。
- Time:编译所花费的时间(毫秒)。
jstat -gc PID
显示Java堆的垃圾回收统计信息,包括堆大小、各代的容量、已使用空间、垃圾回收时间等。
- S0C:幸存者区域0(Survivor 0)的容量(字节)。
- S1C:幸存者区域1(Survivor 1)的容量(字节)。
- S0U:幸存者区域0已使用空间的容量(字节)。
- S1U:幸存者区域1已使用空间的容量(字节)。
- EC:Eden区的容量(字节)。
- EU:Eden区已使用空间的容量(字节)。
- OC:老年代的容量(字节)。
- OU:老年代已使用空间的容量(字节)。
- PC:持久代的容量(字节)。
- PU:持久代已使用空间的容量(字节)。
- YGC:新生代垃圾回收的次数。
- YGCT:新生代垃圾回收所花费的时间(秒)。
- FGC:老年代垃圾回收的次数。
- FGCT:老年代垃圾回收所花费的时间(秒)。
- GCT:垃圾回收总共所花费的时间(秒)。
jstat -gccapacity PID
显示Java堆的容量相关信息,包括堆大小、各代的容量、已用空间等。
- NGCMN:新生代最小容量(字节)。
- NGCMX:新生代最大容量(字节)。
- NGC:新生代当前容量(字节)。
- S0C:幸存者区域0的容量(字节)。
- S1C:幸存者区域1的容量(字节)。
- EC:Eden区的容量(字节)。
- OGCMN:老年代最小容量(字节)。
- OGCMX:老年代最大容量(字节)。
- OGC:老年代当前容量(字节)。
- OC:压缩类空间的容量(字节)。
- PC:持久代的容量(字节)。
- YGC:新生代垃圾回收的次数。
- FGC:老年代垃圾回收的次数。
jstat -gcutil PID
显示Java堆的垃圾回收情况,包括垃圾回收时间、垃圾回收效率、各代的使用情况等。
- S0:幸存者区域0的使用率(百分比)。
- S1:幸存者区域1的使用率(百分比)。
- E:Eden区的使用率(百分比)。
- O:老年代的使用率(百分比)。
- M:元数据区的使用率(百分比)。
- CCS:压缩类空间的使用率(百分比)。
- YGC:新生代垃圾回收的次数。
- YGCT:新生代垃圾回收所花费的时间(秒)。
- FGC:老年代垃圾回收的次数。
- FGCT:老年代垃圾回收所花费的时间(秒)。
- GCT:垃圾回收总共所花费的时间(秒)。
jstat -gccause PID
显示Java堆的垃圾回收原因,包括垃圾回收的原因、发生的次数等。
- S0:幸存者区域0的使用率(百分比)。
- S1:幸存者区域1的使用率(百分比)。
- E:Eden区的使用率(百分比)。
- O:老年代的使用率(百分比)。
- M:元数据区的使用率(百分比)。
- CCS:压缩类空间的使用率(百分比)。
- YGC:新生代垃圾回收的次数。
- YGCT:新生代垃圾回收所花费的时间(秒)。
- FGC:老年代垃圾回收的次数。
- FGCT:老年代垃圾回收所花费的时间(秒)。
- GCT:垃圾回收总共所花费的时间(秒)。
- LastGcCause:最后一次垃圾回收的原因。
jstat -gcnew PID
显示新生代垃圾回收统计信息,包括容量、使用情况、垃圾回收次数等。
- S0C:幸存者区域0的容量(字节)。
- S1C:幸存者区域1的容量(字节)。
- S0U:幸存者区域0已使用空间的容量(字节)。
- S1U:幸存者区域1已使用空间的容量(字节)。
- TT:拷贝到幸存者区域0的对象占用的时间(秒)。
- MT:拷贝到幸存者区域1的对象占用的时间(秒)。
- DSS:幸存者区域0和幸存者区域1中对象总共占用的空间(字节)。
- EC:Eden区的容量(字节)。
- EU:Eden区已使用空间的容量(字节)。
- YGC:新生代垃圾回收的次数。
- YGCT:新生代垃圾回收所花费的时间(秒)。
jstat -gcnewcapacity PID
显示新生代的容量相关信息,包括容量、已用空间等。
- NGCMN:新生代最小容量(字节)。
- NGCMX:新生代最大容量(字节)。
- NGC:新生代当前容量(字节)。
- S0C:幸存者区域0的容量(字节)。
- S1C:幸存者区域1的容量(字节)。
- EC:Eden区的容量(字节)。
- EU:Eden区已使用空间的容量(字节)。
- YGC:新生代垃圾回收的次数。
jstat -gcold PID
显示老年代垃圾回收统计信息,包括容量、使用情况、垃圾回收次数等。
- PC:永久代/元数据区的容量(字节)。
- PU:永久代/元数据区已使用空间的容量(字节)。
- MC:元空间容量(字节)。
- MU:元空间已使用空间的容量(字节)。
- CCS:压缩类空间的容量(字节)。
- CCSU:压缩类空间已使用空间的容量(字节)。
- OC:老年代的容量(字节)。
- OU:老年代已使用空间的容量(字节)。
- YGC:新生代垃圾回收的次数。
- YGCT:新生代垃圾回收所花费的时间(秒)。
- FGC:老年代垃圾回收的次数。
- FGCT:老年代垃圾回收所花费的时间(秒)。
- GCT:垃圾回收总共所花费的时间(秒)。
jstat -gcoldcapacity PID
显示老年代的容量相关信息,包括容量、已用空间等。
- OGCMN:老年代最小容量(字节)。
- OGCMX:老年代最大容量(字节)。
- OGC:老年代当前容量(字节)。
- OC:压缩类空间的容量(字节)。
- PC:永久代/元数据区的容量(字节)。
- YGC:新生代垃圾回收的次数。
- FGC:老年代垃圾回收的次数。
jstat -printcompilation PID
显示JIT编译器的编译方法列表。
- Compiled:已编译方法的数量。
- Size:编译方法占用的空间(字节)。
- Type:编译方法的类型。
- Method:已编译方法的名称。
jmap -histo PID
显示Java堆中对象的统计信息,包括对象类型、数量、大小等。
- 类的实例数和占用的空间的统计信息。
jmap -heap PID
显示Java堆的详细信息,包括堆大小、各代的容量、已使用空间等。
- 类加载器的统计信息。
- 堆的详细信息,包括堆大小、已使用空间、GC信息等。
jmap -dump:live,format=b,file=/tmp/m.hprof PID
保存内存的堆栈为文件
jhat -J-Xmx2048m -port 5000 /tmp/m.hprof
在线查看堆文件的类,速度比较慢
jcmd PID GC.run
强制gc
如何排查?
1.开发环境和测试环境调试
使用jdk自带的jvisualvm.exe,查看占空间的类和实例最多的类,找到其最近的内存释放点一般就是内存泄漏的对象。
也可以使用jmap查看jvm进程实例最多的类。
- 本机启动程序,postman或jmeter调用接口,可以直接用jvisualvm查看堆栈信息
- 远程调试,在测试环境启用jmxremot,如下:
Dcom.sun.management.jmxremote.port=10096 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
但是堆栈信息只能先保存在测试服务器上,下载到本地后再用jvisualvm.exe打开
2.保存生产环境的内存进行分析
先使用jmap命令将程序的内存数据保存下来,jmap -dump:live,format=b,file=m.hporf 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>
java启动参数的顺序调整
生产环境的spring boot工程-Xmx出现有的生效,有的无效,并且所有的jar包jvm参数全放到jar包后面。调整一下顺序,如下:
java -jar -Xmx2048m -Xms1024m test.jar --spring.profiles.active=prod
注:本地电脑调整顺序无效,在生产有效。