一、常见导致OOM的原因
1、栈内存溢出,递归、循环调用导致
举例:找出D盘下文件名中含有txt的文件,D盘下有很多文件夹,每个文件夹下又有很多文件夹,如果使用递归可能导致栈溢出,如何解决?
解决方案:创建一个有边界的队列,先将根目录下的文件夹全部放入队列。然后创建一个线程消费队列,每取出一个文件夹,就将这个文件夹的子文件夹再入队
2、 堆内存溢出,对象太多导致
3、永久代溢出,类太多导致(通常是反射造成的)
举例:Enhancer不断创建代理对象,导致永久代溢出
解决方案:将Enhancer对象缓存
二、OOM时自动dump快照
添加JVM启动参数
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/app/oom
JDK8的JVM模板
-Xms1024M
-Xmx1024M
-Xmn512M
-Xss1M
-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=92
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./
三、第八十六课 案例分析 Tomcat堆内存溢出
1、案例描述
项目使用Tomcat运行时发生OOM异常
2、排查
1)查询日志,发现是堆溢出,并且是Tomcat工作线程报的堆内存溢出
2)使用MAT分析快照,发现是有大量的byte[]数组占据了8G的内存空间
3、原因
1)超时时间设置过长,RPC故障时,请求会卡住,当并发量较大时,对象无法及时被释放,可能导致OOM
2)Tomcat的max-http-header-size属性,tomcat在处理请求时会创建2个数组,每个数组的大小就是上述配置的值。当条件1发生时,更容易导致OOM
4、解决方案
1)修改超时时间,设置为1秒
2)将max-http-header-size设置小一点
四、第八十七课 案例分析 NIO导致堆外内存溢出
1、案例描述
项目使用Jetty服务器运行,发生OOM异常
2、排查
1)查询日志,发现是Direct Buffer memory报内存溢出,即堆外内存溢出
3、原因
代码中创建DirectByteBuffer对象占用堆外内存,但由于新生代较小,老年代太大。所以young gc后DirectByteBuffer对象进入老年代,而由于老年代太大,导致一直无法发生full gc,堆外内存无法释放,所以导致了堆外内存溢出
注意:正常情况下NIO已经考虑了这个问题了,所以NIO源码里当发现无法获取堆外内存时,会主动调用System.gc方法,因此正常情况下是不会堆外内存溢出的。但如果在JVM中又设置了
-XX:+DisableExplicitGC
这个参数会导致System.gc失效,因子导致堆外内存溢出
4、解决方案
合理分配内存
去掉-XX:+DisableExplicitGC这个配置
五、第八十八课 案例分析 RPC导致OOM
1、案例描述
基于Thrift封装了一个RPC框架,平时是用服务A调用服务B,某一日更新服务A的代码并重新部署后,服务B却因为堆内存溢出挂掉了,重启服务B后没多久,服务B又因为堆内存溢出挂掉了
2、排查
1)查询日志,发现发生OOM的位置,是自研的RPC框架
2)使用MAT分析快照,发现占用内存最大的是一个byte[]数组,这个数组就是RPC框架内部的类引用的
3、原因
服务A发送请求时,会把对象序列化给服务B,服务B再反序列化。但服务A对类C添加了字段,而服务B中的类C还是原来的,导致服务B反序列化失败,而自研的RPC框架中,一旦反序列化失败,就会开辟一个4G的byte[]数组,把这个序列化字节流放进去,最终导致OOM
4、解决方案
将4G的byte[]数组改成4M就可以了
六、第九十三课 案例分析 类加载器过多导致服务假死
1、案例描述
上游服务反馈经常一段时间内无法访问服务接口,过一会又可以访问了
2、排查
1)查询日志,并没有发现OOM异常
2)jstat查询gc,怀疑是否由于频繁full gc,stop the world导致服务假死
但通过观察发现,虽然频繁发生gc,但上游服务器并没有反馈服务假死
3)CPU负载过高,导致进程无法获得CPU去执行
但通过top命令观察,发现CPU消费很少,所以不是这个原因
4)内存使用率过高
通过top命令观察,发现JVM消耗超过50%的内存了
3、原因
JVM占用超过50%的内存,而系统本身和其他的进程也要耗费内存。所以有时候JVM的内存虽然没有达到上限,不会发生OOM,但是系统此时内存也占满了,JVM无法申请到内存,于是系统会将这个进程杀掉。但由于这个JVM进程有监控脚本,一旦进程被杀掉,脚本又把进程重新启动起来。所以上游服务会发现有时候调不通,有时候又能调通
4、解决方案
通过MAT找出占用JVM过多内存的原因,发现是创建了过多的类加载器,修改优化代码即可