记一次内存泄漏排查过程

引言

最近公司一项目写完在进行压测,在压测过程中偶然top了几次,发现项目运行的进程占用的资源RES一直在缓慢增长。于是怀疑是存在内存泄漏。

用通俗的话来讲内存泄漏是由于开发人员没有注意到内存管理,没有有效的进行内存回收导致的一部分内存无法被回收,同时在系统运行过程中会不断有新的相关内存占用,最终导致Out of Memory。这种情况在C++ programmer中是非常头疼的一件事,因为C++把内存管理的权限交给了开发人员,但是对于Java开发人员来说会轻松很多,因为JVM自带GC收集器垃圾内存回收一般不需要开发人员关心。那么有人会问了,既然如此Java程序有可能会出现内存泄漏情况吗?答案是肯定的。那么为什么在有GC收集器的情况下还会有内存泄露呢?这还需要搞清楚JVM的垃圾回收的原理--什么样的对象会被认为是垃圾

垃圾回收算法

Java的JVM种类繁多,以Hotspot JVM为例,Hotspot使用的是根搜索算法,根搜索算法基本原理是定义好一个或多个GC Root对象,从这些GC Root对象开始向下遍历该对象的引用对象,遍历走过的路径叫引用链,如果一个对象没有一条引用链可达,那么这个对象和其引用链都会被标记为垃圾,会被GC收集器回收掉。在JAVA语言中,可以作为GC Root的对象有如下几种:

  • 虚拟机栈(栈帧中d额本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈JNI(native方法)的引用对象

了解了垃圾回收算法,我们就有理由相信JAVA程序也可能出现内存泄漏的,如果我们开发人员不注意写了一些对象被GC Root对象引用了,并且没有做内存释放操作,比如说一些集合的操作,尤其是类全局变量的集合,一有操作不慎就会造成内存泄漏。

排查过程

说了这么多,现在来看看本人经历的一次内存泄露排查过程。背景上面已经介绍了,项目就是一个简单的netty程序。当发现可能有内存泄漏后,做的第一件事就是排查代码,毕竟是自己写的代码,自己最熟悉,在一通排查后并没有发现有明显集合数据添加了没有remove的操作,当时是懵逼的,因为有add就有正常remove,没办法只能祭出杀手锏使用jmap、jstat和三方内存泄漏分析工具--memory analyzer。 首先使用jstat -gcutil pid 6000命令每6秒查看一次gc情况,观察一段时间后发现,项目运行一天后疯狂进行Full GC,这下可以断定绝对有内存泄露。然后使用jmap -histo:live pid | head -10命令查看系统运行进程存活对象数量和占用内存前五的对象,如下图:

 num     #instances         #bytes  class name
----------------------------------------------
   1:           278       16903672  [B
   2:          8766         819424  [C
   3:          3454         385560  java.lang.Class
   4:          2777         305984  [Ljava.lang.Object;
   5:          6858         219456  java.util.concurrent.ConcurrentHashMap$Node
   6:          8716         209184  java.lang.String
   7:          6438         103008  java.lang.Object

发现排名第一多对象数量和占用内存最多的竟然是一个Long对象,这绝对有问题,一般没有内存泄漏的系统jmap出来的前五一般都是byte、char、string这些对象,知道这个Long有问题,本人又去代码中瞧了一眼,用了Long的地方都是正常的,有remove释放操作,为了进一步弄清到底是哪个地方出问题,我祭出了终极大法--Memory Analyzer。使用jmap -dump:format=b,file=文件名 [pid] 命令dump出来内存映像文件,然后扔进Memory Analyzer,进入histoGram,选择Long这个对象,右键选择with income object。可以找到引用链,最终发现是一个别的同事写的私用协议有个全局字段add到队列中但是没有remove,所以导致内存泄漏。

对了,最后说一嘴之前我不是使用top看内存涨了么,其实使用top看是看不出什么的,因为就算没有内存泄漏,特么RES也是会涨的,因为JVM向操作系统要内存了之后,如果当前内存够,JVM是不会立马释放内存的,JVM还会占用这块内存当做缓存,防止数据高峰时又需要向操作系统要内存(因为系统调用是要时间和性能做代价的)。

猜你喜欢

转载自blog.csdn.net/nethackatschool/article/details/80145774