今天带着大家看看堆内存溢出我们一般怎么排查的,在排查之前,我想JVM的基础知识大家应该都有了解吧?我带大家回温一下JVM的内存模型(这跟JAVA内存模型JMM可不一样,不要记错了)
内存溢出是发送在堆中的。JVM堆内存被分为两部分:年轻代(Young Generation)和老年代(Old Generation)。
年轻代
年轻代是所有新对象产生的地方。当年轻代内存空间被用完时,就会触发垃圾回收。这个垃圾回收叫做Minor GC。
年轻代被分为3个部分——Enden区和两个Survivor区。
年轻代空间的要点:
-
大多数新建的对象都位于Eden区。
-
当Eden区被对象填满时,就会执行Minor GC,并把所有存活下来的对象转移到其中一个survivor区。
-
Minor GC同样会检查存活下来的对象,并把它们转移到另一个survivor区。这样在一段时间内,总会有一个空的survivor区。
-
经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间,通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的。
年老代
年老代内存里包含了长期存活的对象和经过多次Minor GC后依然存活下来的对象,通常会在老年代内存被占满时进行垃圾回收。
GC种类
1、Major GC
老年代的垃圾收集叫做Major GC,Major GC通常是跟full GC是等价的,收集整个GC堆。
2、分代GC
-
Young GC:只收集年轻代的GC
-
Old GC:只收集年老代的GC(只有CMS的concurrent collection是这个模式)
-
Mixed GC:收集整个young gen以及部分old gen的GC(只有G1有这个模式)
3、Full GC
Full GC定义是相对明确的,就是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。
大家可以从上图看到年轻代分为了一个Eden区和两个survivor区(S1,S2),survivor区同一时间只会有一个满一个空,交替的。
然后就是GC到一定的阈值到老年代,今天不讲永久代所以忽略Mataspace。
那要怎么分析呢
今天我就用一个JDK自带的工具jvisualvm来给大家演示一波怎么操作的,因为这玩意谁都有,你去命令行敲一下jvisualvm就出来了(Mac是这样的,不知道Windows是怎么样子的)。
操作界面
一般什么情况可能是出现了溢出
超时,不进行服务,服务挂掉,接口不在服务这样的异常问题。那模拟也很简单,我写个循环一直往List丢数据,不使用list就能看到现象了
大家可以看到图形化界面还是很清晰明了的,这个是Visual GC的插件
大家点击菜单栏的插件,然后安装就好了,安装完了记得点击激活。
可以看到不释放,堆空间就一直上去,直到OOM(out of memory)
这个时候我们就dump下来堆信息看看
会dump出一个这样的hprof快照文件,可以用jvisualvm本身的系统去分析,我这里推荐MAT吧,因为我习惯这个了。
MAT(https://www.eclipse.org/mat/downloads.php)下来好了我们可以看到mat已经分析了我们的文件
你看他就是个暖男,都帮我们分析出来了一个问题,我们点进去看看
他发现了是ArrayList的问题了,我们再往下看看
看到了嘛,具体代码的位置都帮我们定位好了,那排查也就是手到擒来的事情了。
延伸点
上面我们使用工具dump了,那怎么去服务器上dump呢?
jmap -dump:format=b,file=<dumpfile.hprof> <pid>
有朋友可能问了,不是所有的故障当时我们都在场的,无法及时dump,那也简单
-XX:+HeapDumpOnOutOfMemoryError
配置这玩意之后,oom的时候会自动dump的,到时候拿快照分析一波就好了。
MAT的功能还有很多的,百度谷歌太多工具文了,我就不做重复的工作了,比如还可以排查对象的强弱引用,还可以查看引用链等等。
整理了学习资料以及学习视频,送给小伙伴们。公号内回复【学习资料】自行领取。和一些小伙伴们建了一个技术交流群,一起探讨技术、分享技术资料,旨在共同学习进步,如果感兴趣就扫码加入我们吧!