因为本项目出现了一次对内存溢出的问题,于是做了一次虚拟机内存的分析,使用 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 中,如下图所示:
可以看到 org.apache.catalina.session.StandardSession 对象的个数达到惊人的554326 个!
于是猜测 tomcat 并未销毁曾经连接过的会话,每次重启tomcat会把历史的会话从新加载进来。
为了印证这个猜想,在重启tomcat的同时,用 jstat 来监控启动过程中老年代的变化情况,如下图所示:
观察 OU (老年代已使用内存容量),可以看到 ,在tomcat启动过程中 老年代突然从1706KB 增加到510625KB 。
查阅 tomcat 的 conf 目录下 context.xml,里面有这么一段话:
缺省情况下,
如果不想保留的话,需要把
于是将改注释去掉,重启启动tomcat,发现OU容量维持在1706KB的正常范围内。
但是事情并没有结束,在 tomcat 启动了半天之后,(这半天基本没有访问请求),却发现有几次不该有的FGC,
因为老年代非常平稳,远远达不到需要FGC的程度,因此猜测某处代码强制执行了垃圾回收 System.gc() , 用 jstat -gccause pid 查看最近一次FGC 原因:
于是尝试为 tomcat 添加启动参数 -XX:+ DisableExplicitGC 来禁止System.gc() 的调用。重新启动 tomcat ,观察堆内存和gc情况:
发现本次启动没有发生FGC,总的垃圾回收时间明显减少,本次优化完成!。