案例背景:一次我们线上推了一个大促销活动,大致就是类似于在某一个特定的节日里,突然给所有的用户发短信,邮件,APP Push消息,说现在有个也别优惠的活动,这类大促销活动一般都会吸引比平时多几倍的用户短时间内突然登录APP来参与,所以系统一般在这个时间压力会比平时大好几倍,但是因为从系统内的整体设计角度而言,其实给的一些数据库,缓存和机器的资源都是足够的,所以通常而言不应该有什么问题。但是那次大促销活动开始之后,直接导致线上一个系统的CPU使用率飙升,而且以为CPU使用率太高,导致系统几乎陷入卡死的状态,无法处理任何请求。
初步排查CPU负载过高的原因
常见的CPU负载过高的两个场景。
第一个场景:是你自己在系统里创建了大量的线程,这些线程同时并发运行,而且工作负载都何种,过多的线程同时并发运行就会导致你的机器的CPU负载过高。
第二个场景:就是你的机器上运行的JVM在执行频繁的Full GC,Full GC是非常耗费CPU资源的,他是一个非常重负载的过程。
初步排查频繁Full GC的问题
创建频繁Full GC的原因
- 内存分配不合理,导致对象频繁进入老年代,进而引发频繁的Full GC;
- 存在内存泄漏等问题,就是内存里驻留了大量的对象塞满了老年代,导致稍微有一些对象进入老年代就会引发Full GC;
- 永久代里的类太多,触发了Full GC
- system.gc()
通过jstat分析了一下线上系统的情况,发现并不存在内存分配不合理,对象频繁进入老年代的问题,而且永久代的内存使用也是很正常,所以上述原因中最可能存在的一个问题就是老年代驻留了很多对象,发生了内存泄漏的问题。
常见的内存分析工具
第一种:jmap+jhat
第二种:MAT
之前我们介绍过jmap+jhat的组合来分析内存里的大对象,今天我们介绍另外一个常用的强有力的工具,MAT。
通过jmap命令导出一份线上系统的内存快照即可:
jmap -dump:format=b,file=文件名[服务进程ID]如何使用MAT进行内存分析
不少人是通过Eclipse集成的MAT插件来使用的,但是很多人其实开发是用IntelliJ IDEA的,所以这个时候可以直接下载一个MAT是用即可。
下载安装完成之后,在对应的配置文件中有一个文件名为MemoryAnalyzer.ini,如果我们导出的dump文件较大,需要重置初始化的内存大小,默认为1G。
启动MAT,选择一个Dump文件。
该案例结果内存分析之后发现出现内存泄漏的原因是:代码中做了一个JVM的本地缓存,把很多数据都加在到了内存里缓存起来,但是没有限制本地缓存的大小也没有使用LRU算法定期淘汰一些缓存的数据,导致缓存内的对象越来越多,进而造成了内存泄漏。
如何解决这个问题:使用类似EHCache之类的缓存框架就可以,固定了缓存的大小,定期淘汰删除掉一些不怎么访问的缓存。