文章目录

  • 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三个压缩文件就是报告,可以看到程序的运行概况,最大的对象,和推测泄露点。

boot文件 设置java内存 spring boot 内存_boot文件 设置java内存


从我的内存泄露报告中可以看到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

注:本地电脑调整顺序无效,在生产有效。