记一次内存溢出OutOfMemoryError: Java heap space
起因是有一个凌晨两点的定时服务在运行是内存溢出了,出现概率不是很高,在本地运行后发现定时任务占用内存并不高,怀疑其他代码有内存泄露问题,
这是程序没运行、运行后3分钟、运行后8小时后的内存 占用情况,通过top命令确定是java程序占用的内存,程序占用内存越来越高,怀疑有内存泄露问题
接下来我们设置最大堆内存使用大小,设置出现堆内存溢出时将快照文件保存到指定目录
nohup java -jar -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/root/dump_OOME.hprof FEBS-Shiro-2.0.jar &
-XX:+HeapDumpOnOutOfMemoryError //虚拟机在堆异常时生成对存储快照,后缀一般是.hprof -Xms //Java堆的最小值,例如-Xms20m,将Java堆的最小值设置为20MB -Xmx //Java堆的最大值,例如-Xms40m,将Java堆的最大值设置为40MB
等待程序出现OOM错误,收集快照文件。由于程序等了一天没有OOM错,且程序一直占用大量内存,于是我们手动保存快照文件
使用jmap 工具
将jvm 内存快照保存到本地
由于我服务器使用的是openjdk 默认安装并没有jmap 工具 需要下载
- 查看jdk 版本
[root@iZ2ze9uwnp0zegoakytcyuZ jvm]# java -version
openjdk version "1.8.0_262"
OpenJDK Runtime Environment (build 1.8.0_262-b10)
OpenJDK 64-Bit Server VM (build 25.262-b10, mixed mode)
- 查看openJDK有jmap的yum源
[root@iZ2ze9uwnp0zegoakytcyuZ jvm]# yum whatprovides '*/jmap' | grep 1.8.0
1:java-1.8.0-openjdk-devel-1.8.0.242.b08-1.el7.i686 : OpenJDK Development
Filename : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.el7.i386/bin/jmap
1:java-1.8.0-openjdk-devel-1.8.0.242.b08-1.el7.x86_64 : OpenJDK Development
Filename : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.el7.x86_64/bin/jmap
1:java-1.8.0-openjdk-devel-1.8.0.252.b09-2.el7_8.i686 : OpenJDK Development
Filename : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.i386/bin/jmap
1:java-1.8.0-openjdk-devel-1.8.0.252.b09-2.el7_8.x86_64 : OpenJDK Development
Filename : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/bin/jmap
1:java-1.8.0-openjdk-devel-1.8.0.262.b10-0.el7_8.i686 : OpenJDK Development
Filename : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.i386/bin/jmap
1:java-1.8.0-openjdk-devel-1.8.0.262.b10-0.el7_8.x86_64 : OpenJDK Development
Filename : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/bin/jmap
- 选择安装对应的版本,我这里安装
java-1.8.0-openjdk-devel-1.8.0.262.b10-0.el7_8.x86_64
这个版本,注意要对应本地的版本
yum install java-1.8.0-openjdk-devel-1.8.0.262.b10-0.el7_8.x86_64
- 测试 安装是否成功
[root@iZ2ze9uwnp0zegoakytcyuZ jvm]# jmap
Usage:
jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
- 先通过
jmap -histo pid
的方式将对象使用情况导出到文件
jmap -histo 2596 > me.txt
查看后发现 [Ljava.util.HashMap$Node; 占用较大内存
#对象说明
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
- 接着导出快照文件
[root@iZ2ze9uwnp0zegoakytcyuZ jvm]# ps -ef | grep java
root 25185 17755 0 15:46 pts/0 00:00:00 grep --color=auto java
root 26843 1 0 09:51 ? 00:02:55 java -jar -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/root/dump_OOME.hprof FEBS-Shiro-2.0.jar
[root@iZ2ze9uwnp0zegoakytcyuZ ~]# jmap -dump:live,format=b,file=m.hprof 26843
Dumping heap to /root/m.hprof ...
Heap dump file created
一个快照400多M 离谱
下载到本地
通过MAT工具对dump文件进行分析
mat工具介绍
Histogram 可以列出内存中每个对象的名字、数量以及大小。
Top Consumers 直接展示出我们内存中的大内存
Dominator Tree 会将所有内存中的对象按大小进行排序,并且我们可以分析对象之间的引用结构。
Leak Suspects,直接给出分析,分析出我们代码中可能会出现内存泄漏的地方
其余action不常用
Histogram 视图
Histogram 视图 列出每个class产生了多少个实例,以及占有多大内存,所占百分比,可以很容易找出占用内存最多的几个类,根据Retained Heap排序,找出前几个。
可以分不同的维度来查看类的Histogram视图,Group by class、Group by superclass、Group by class loader、Group by package 只要有溢出,时间久了,溢出类的实例数量或者其占有的内存会越来越多,排名也就越来越前,通过多次对比不同时间点下的Histogram图对比就能很容易把溢出类找出来。
Histogram可以展示某个特定类的对象个数和每个对象使用的内存。当然char[],String和Object[]都不太会导致内存问题。为了更好的组织这个视图,你可以通过classloader或者package来分组。这会让你更好的专注在你自己的对象上。
通过这个视图我们可以看见HashMap$Node[] 这个对象占据非常高的内存,非常可疑。我们也可以看见每一个对象正在占用的内存数量。这里有两个数值,Shallow Heap和Retained Heap。Shallow heap是一个对象消费的内存数量。每个对象的引用需要32(或者64 bits,基于你的CPU架构)。基本数据类型例如整形和长整形需要4或者8 bytes以及其他的。其实更有用的参数是Retained Heap.
Top Consumers 内存中的大内存
Leak Suspects分析出我们代码中可能会出现内存泄漏的地方
Dominator Tree 支配树
列出每个对象(Object instance)与其引用关系的树状结构,还包含了占有多大内存,所占百分比 可以很容易的找出占用内存最多的几个对象,根据Percentage(百分比)来排序。 可以分不同维度来查看对象的Dominator Tree视图,Group by class、Group by class loader、Group by package 和Histogram类似,时间久了,通过多次对比也可以把溢出对象找出来,Dominator Tree和Histogram的区别是站的角度不一样,Histogram是站在类的角度上去看,Dominator Tree是站的对象实例的角度上看,Dominator Tree可以更方便的看出其引用关系。
另外大家应该可以注意到,在每一行的最左边都有一个文件型的图标,这些图标有的左下角带有一个黄色的点,有的则没有。带有红点的对象就表示是可以被GC Roots访问到的,根据上面的讲解,可以被GC Root访问到的对象都是无法被回收的。那么这就说明所有带红色的对象都是泄漏的对象吗?当然不是,因为有些对象系统需要一直使用,本来就不应该被回收。我们可以注意到,上图当中所有带黄点的对象最右边都有写一个System Class,说明这是一个由系统管理的对象,并不是由我们自己创建并导致内存泄漏的对象。
首先Retained Heap表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存,因此从上图中看,前五行的Retained Heap是最大的,我们分析内存泄漏时,内存最大的对象也是最应该去怀疑的。
从上图可以看出头几个对象占用的内存百分比非常低,那么结合上面的疑似泄露点下方的许多sun.security.ssl.SSLSessionContextImpl
就变得非常可疑
通过右键 Merge Shortest Paths to GC Roots
> whth all references
查看对象的引用
至此泄露点已经找到,接下来就是分析为什么会泄露了。
解决
参考https://blog.csdn.net/fedorafrog/article/details/107015711 后
- 修改mysql url
useSSL=false
- 修改 hikari中 闲连接存活最大时间
idle-timeout: 0
空闲连接存活最大时间, 0则表示空闲的连接在连接池中永远不被移除 - 修改
max-lifetime: 28700000
连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
问题已经解决
程序运行2天后
关键词
- Objects: 这个类有多少个实例
- Shallow Heap: 单个实例占用的内存
- Retained Heap: 单个实例及其引用一共占有多少内存
- Percentage :百分比
- list objects -- with outgoing references : 查看这个对象持有的外部对象引用。
- list objects -- with incoming references : 查看这个对象被哪些外部对象引用。
- show objects by class -- with outgoing references :查看这个对象类型持有的外部对象引用
- show objects by class -- with incoming references :查看这个对象类型被哪些外部对象引用
参考
https://www.cnblogs.com/shihaiming/p/11389386.html 使用Eclipse Memory Analyzer进行内存泄漏分析
https://www.jianshu.com/p/759e02c1feee 关于使用Eclipse Memory Analyzer的10点小技巧
https://blog.csdn.net/fedorafrog/article/details/107015711 Druid数据库连接池引起的FullGC问题排查、分析、解决
https://blog.csdn.net/a740169405/article/details/53610689 Shallow Heap 和 Retained Heap的区别
https://blog.csdn.net/fedorafrog/article/details/107015711 Druid数据库连接池引起的FullGC问题排查、分析、解决
https://blog.csdn.net/kusedexingfu/article/details/108347296 MAT内存分析工具介绍
https://www.eclipse.org/mat/downloads.php mat工具安装
标题:记一次内存溢出OutOfMemoryError: Java heap space
作者:zhaojishun
地址:http://blog.zhaojishun.cn/articles/2020/09/16/1600255334410.html