记一次 Tomcat 项目内存溢出排查

因为本项目出现了一次对内存溢出的问题,于是做了一次虚拟机内存的分析,使用 jstat工具查看了下 tomcat 进程的内存使用情况,

jmap -gc pid 1000 10

发现老年代已使用容量已经到达 100%,而且 FGC 次数比 YGC 次数还多,初步分析老年代中存在大量无法被回收的内存。

1、导出dump文件

为了查明是哪些对象无法被垃圾回收,用 jmap 命令  dump 出了内存堆文件

 jmap -dump:format=b,file=/usr/local/tomcat7/heap-zjym2.bin pid

2、分析dump文件

用Eclipse Memory Analzyer 工具分析dump文件,由于dump文件比较大,有2G多,因此在导入dump文件之前需要设置Eclipse Memory Analzyer 的堆内存容量,如-Xmx2048m; 

导入后分析结果如下:

Oneinstance of "org.apache.catalina.session.StandardManager" loaded by "org.apache.catalina.loader.StandardClassLoader @ 0x6c0000230" occupies 1,192,391,064 (94.58%) bytes. The memory is accumulated in one instance of "java.util.concurrent.ConcurrentHashMap$Segment[]" loaded by "".

显然org.apache.catalina.session.StandardManager  这个对象中存在大量无法被回收的ConcurrentHashMap引用,而这个类是tomcat用来管理session对象的管理类 ,ConcurrentHashMap  中保存的是 org.apache.catalina.session.StandardSession,每个tomcat会话都会创建一个StandardSession,并放入到改ConcurrentHashMap  中,如下图所示:

记一次 Tomcat 项目内存溢出排查_第1张图片

可以看到 org.apache.catalina.session.StandardSession 对象的个数达到惊人的554326 个!

于是猜测 tomcat 并未销毁曾经连接过的会话,每次重启tomcat会把历史的会话从新加载进来。

为了印证这个猜想,在重启tomcat的同时,用 jstat 来监控启动过程中老年代的变化情况,如下图所示:

记一次 Tomcat 项目内存溢出排查_第2张图片

观察 OU (老年代已使用内存容量),可以看到 ,在tomcat启动过程中 老年代突然从1706KB 增加到510625KB 。

查阅 tomcat 的 conf 目录下 context.xml,里面有这么一段话: 

 

 

缺省情况下,是被注释掉的,此时,tomcat在每次重新启动的时候,都会保留原来的session。 

如果不想保留的话,需要把的注释放开,这样tomcat关闭的时候,会删掉所有的session。 

于是将改注释去掉,重启启动tomcat,发现OU容量维持在1706KB的正常范围内。

但是事情并没有结束,在 tomcat 启动了半天之后,(这半天基本没有访问请求),却发现有几次不该有的FGC,

记一次 Tomcat 项目内存溢出排查_第3张图片

因为老年代非常平稳,远远达不到需要FGC的程度,因此猜测某处代码强制执行了垃圾回收 System.gc() , 用 jstat -gccause pid 查看最近一次FGC 原因:

215713_dt5d_3656484.png

于是尝试为 tomcat 添加启动参数 -XX:+ DisableExplicitGC 来禁止System.gc() 的调用。重新启动 tomcat ,观察堆内存和gc情况:

记一次 Tomcat 项目内存溢出排查_第4张图片

发现本次启动没有发生FGC,总的垃圾回收时间明显减少,本次优化完成!。

 

转载于:https://my.oschina.net/u/3656484/blog/1517701

你可能感兴趣的:(记一次 Tomcat 项目内存溢出排查)