作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
学习必须往深处挖,挖的越深,基础越扎实!
阶段1、深入多线程
阶段2、深入多线程设计模式
阶段3、深入juc源码解析
阶段4、深入jdk其余源码解析
阶段5、深入jvm源码解析
一、简介
本章,我们将讲解一个已经稳定运行的系统的内存溢出问题,该内存溢出问题的元凶是类加载器,我们先来看下系统的背景。
1.1 系统背景
这个系统已经在线上稳定运行了一段时间,部署在Tomcat中启动。突然有一天收到告警,显示许多访问该系统的调用请求出现假死现象,但是过了一会儿又可以了。经过排查发现,每隔一段时间,系统就会出现假死。
一般来说,系统出现假死,接口无法调用,就是系统的资源不足以处理新的请求,所以我们先通过top命令排查下机器的CPU和内存使用情况。
- 如果这个服务大量使用了内存,导致频繁Full GC(这个问题我们之前的章节介绍过),从而引发STW,接口调用就会出现假死现象。
- 如果机器的CPU负载太高,比如某个进程耗尽了CPU资源,那么正常服务就始终无法得到CPU去执行,这也可能导致假死。
我们通过top
命令发现,系统本身对CPU消耗非常少,也就1%,但是却耗费了50%的总内存。要知道,机器配置是4核8G,我们给JVM进程分配的总内存最多也就是4-5G,系统消耗了4-5G的内存,说明JVM中的Java堆内存几乎被占满。
二、问题分析
2.1 内存使用率过高
我们先分析下,进程占用内存过高会导致什么,一般会发生三种情况:
- 频繁Full GC,GC带来的Stop the World导致程序假死;
- 内存占用过多,引起内存溢出;
- 内存使用率过高,导致程序进程因为申请内存不足,直接被操作系统给干掉。
我们先用jstat分析下GC情况,发现确实经常发生GC,但是每次GC耗时也就几百毫秒,程序也没有因此出现假死现象。
我们再排查是否发生了OOM,经过日志分析,程序并没有抛出任何OOM异常。那只有第三种情况了,可能是程序进程被OS杀掉,然后由于自启脚本又重新启动了,但是在这段时间内,程序无法被访问,就出现了假死的现象。
2.2 JVM参数不合理
我们通过MAT分析dump出的内存快照,发现有一大堆的ClassLoader占用超过了50%的内存。最后,根据MAT层层抽丝剥茧,发现是写这个代码的童鞋搞了个自定义的类加载器,但是代码中无限制的创建了大量的自定义类加载器,重复加载了大量数据,结果一下子把内存耗尽了,导致程序进程被OS杀掉。
三、系统优化
优化方式很明显,就是修改有问题的代码点,避免重复创建自定义的类加载器,避免重复加载大量数据到内存中。
四、总结
本章介绍的案例和之前有点区别,程序其实并没有发生OOM异常,但又确实是因为内存占用过多而被OS杀死。这个案例告诉大家,无论如何优化分析,它们背后的原理都是一套东西,掌握分析问题的思路才是最重要的。