![](https://i-blog.csdnimg.cn/blog_migrate/b86999d9a016aa987cf5f92a223033e3.jpeg)
内存泄漏为什么不容易判断
在性能问题里面内存有没有泄露、如果有泄露是哪里泄露了这两个问题是非常难判断和定位的。甚至有厂商对内存是否健康的评判就是:下次系统重启之前,应用能正常运行。换句话说,有内存泄露找不出来也没关系,定期重启一下服务器,而且在生产环境,的确有些企业是这么干的。
CPU如果利用率异常,可以查哪个进程中的哪个函数占用CPU多,相应的,内存也可以查哪个进程占内存多。为什么内存问题不像CPU问题那么容易定位?
内存泄露是指使用内存完成后没有释放,内存增长并不能分辨增长出来的内存是进程真正要用的,还是进程泄露出来的。而CPU的占用是瞬时的、确定的,不存在某个进程申请了CPU占着不用的情况。
在一个讨论组里,有人提问:对一个基于Java的Web系统进行压力测试,如果虚拟用户数从峰值下滑的同时,内存占用率却保持在峰值不变,是否能得出Java程序存在内存泄露的问题?
回答是否定的,原因如下:
首先,内存占用率指的是什么内存的占用率
在不同的OS上有不同的内存管理机制,比如AIX上,我们最关注是计算内存,但如果内存利用率指的是计算内存+非计算内存的话,即使内存占用率上升也说明不了太多问题。再比如Linux上,我们最关注是active内存,如果内存利用率指的是active+buffer+cache,即使内存占用率上升也说明不了太多问题。
第二,其他进程的干扰
操作系统上运行的进程千千万,内存不下降,可能是其他应用/系统进程对内存的使用,应具体分辨是哪个进程占据了内存。因此考察是否有内存泄露应关注的是指定进程有没有内存增长,这样比较容易排除干扰。不过,查看进程的Data Segment也只能查看这个进程使用的一部分内存,而这个进程间使用的Shared Memory Segment则不在这个指标中,但同样需要关注。
第三,进程池的原因
假如只关注计算内存(AIX),如果服务端的应用是一个100个进程的进程池,应用刚启动的时候没有客户端的连接进来,因此没有启动任何进程,随着客户连接的增多,100个进程统统启动,并常驻内存;再假如这些进程使用的内存是分配好不变的,那么内存占用率保持在峰值不变,是很正常的表现。
CICS里面也有进程常驻内存的概念。常驻内存后,进程不会掉下去,因此没有创建、销毁进程的开销。
第四,JVM内存管理的原因
如果不是Java程序,内存不下降甚至内存上升,也有上述的多种原因,何况这是Java程序,存在一个JVM内存管理机制的原因。
我们曾经遇到这样一个案例。对某Linux服务器上的某应用进行压力测试,在一周的测试过程中,发现内存不断增加。尽管服务器上每天定时清理内存(如下),但总趋势仍然是内存增长。
![](https://i-blog.csdnimg.cn/blog_migrate/5e106b97ff5f647a314f68782976bc69.png)
备注:drop_caches是清理无用的cache,对于dirty状态的是不清理的,直到dirty的内存被写入磁盘。但如果用sync操作把dirty的内存flush到磁盘中,后续的drop_caches将释放更多的内存。
后续我们就发现,这是JVM内存管理机制造成的内存泄露假象。该系统在测试过程中Java full GC(全量垃圾回收)没有被调起,老年代的内存没法被释放。虽然应用使用的内存并没有超过JVM设定的heap大小,但从Linux内存监控的指标上看,active内存是不断增加的。
为什么full GC没有被调起呢?这个场景下,老年代内存的增长会非常缓慢,几天内都不会达到触发full GC的标准,以致出现内存使用量不断增长不回收的现象。并且,这个Java应用是一个独立的Java程序,并没有运行在应用中间件上,因此没有中间件帮它做合理的GC策略,而应用本身也没有去调起full GC。
后经调整应用,主动调起full GC,内存增长问题得到解决。
第五,本应用其他逻辑的干扰
也许这个应用是个接收客户端数据报送并进行ETL处理的程序,服务端的应用在收到客户端的数据后,开始启动其他进程/线程/模块去做后续处理,后续处理需要分配内存。
继续最初的问题,回到那个基于Java的Web系统,如果虚拟用户数从峰值下滑到0,内存占用率却继续上扬,是否能得出Java程序存在内存泄露的问题?
回答仍然是否,可能的原因还是上面那几条。
总而言之,内存泄露是非常难判断的事,需要长时间的测试才能得到猜测性结论。