经历了内存泄漏的骚扰,记录一下内存泄漏时的分析手段。
1. 遇到问题及解决办法
a、根据志强邮件中的附件1048.txt,像是有对象泄漏。
查本地的缓存:找到GlobalMemocache中有ThreahLocal的本地缓存实现,怀疑是它的问题。
后经多次更新,实践证实,跟GlobalMemocache无关。
b、dump出来内存镜像。发现一百多个百万级的对象数组。
用MAT分析出这个一百多个百万级的对象数组都来自BlogPhotoAuditBiz。看源码。有单例实现。
自己模拟这种代码实现测试,发现spring可以通过私有的无参构造方法完成构造。出现两个实例。
然后就让志强邮件代码的开发者,希望发现同类问题。结果:无同类问题。
开发者把数组量由百万降到十万级,问题依旧。
c、查数据库连接:发现有800多个数据库连接。怀疑有问题。
让志强看了下主站的,也有700百。基本排除数据库的问题。
d、查线程锁,有大量对象锁发现。
结果绝大多数为ICE的连接。放弃。
e、看dump出来的数据
看很多Map的Enty,看字符串,看调用关系,ect...浪费了很多时间,无收获。
f、回到线上,观察内存中的实例数变化(只观察com.xiaonei.blog.biz)
发现Service的实例数是整体的增加。像是Rose的容器中的对象都成倍增加。
最后才想到是Rose的容器被手工new了。
总结一下:
第1步的时候我就犯了个错误,如果是本地缓存问题,如果最多的应该是Doing对象。
第2步的时候已经发现BlogPhotoAuditBiz的实例不止两个,顺着这个应该会更快找到原因。
真正的错误是MPage抄了ShareFacade中的错误用法。导致了错误的结果。
附一下用到的几个工具和命令
mat下载地址: www.eclipse.org/mat/downloads.php
其它工具全是jdk自带的
jps
查所有java进程的VM编号,Linux下一般跟进程号pid一致。因此编号存储于/tmp下,较长时间运行的vm编号可能会被自动清理,需要自己建。
jmap -histo:live 15604 > 15604.log
把vm编号为15604的对象分布以列表的形式输出到一个日志文件,以便分析。我同时还用下面这条命令来确定有多少个对象的实例:
grep com.xiaonei.blog.biz 15604.log
jstack 15604
看vm中各线程的状态。包括对象锁的情况。
jmap -heap 15604
看内存使用情况。之前张剑曾经发现Perm区的问题,然后改了Resin的启动参数。这次是Old区的问题,Perm区还没来得及出问题。
kill -3 15604
不是杀进程,是在jvm.log中输出jstack和jmap -heap的内容。比较懒的用法。
jstat -printcompilation 15604
第1步判断的时候,以为这个命令是显示当前vm中执行的指令。看到了ThreadLocal的指令执行。可能是我理解错了,意思不详。
我也犯了另一个错误,看到<bean>配置的时候,在init-method里指定getInstance,就误以为是指定构造。实际上这个是构造完成且属性设置完后后,顺便做的一个调用,只是可以用来做一些初始化操作。
//end
=============================赤色分界线====================================
1. 用jmap查看内存镜,分析各个对象的实例
使用jmap命令:(需要配置JAVA_HOME和PATH变量)
jmap -histo:live processid > /home/zzq/jmap_1150
2. 使用Eclipse Memory Analyzer
jmap -dump:live,format=b,file=heap.bin processid
3. 使用Jprofiler
下载地址:http://www.ej-technologies.com/download/jprofiler/trial
(1) 在本地装好Jprofiler并配置
工具栏Start Center-->New Session选项卡-->New Remote Integration
Local or remote:选 on a remote computer,然后选linux版本
Profiled JVM:根据服务器上的jdk填写
Startup mode:选第一个 Wait for a connection from the JProfiler GUI
Remote address:填写服务器的ip地址
Remote installation directory:/opt/jprofiler6
Choose profiling port: 默认8849
Perform modifications:复制出来,下面用-agentpath:/opt/jap/bin/linux-x86/libjprofilerti.so=port=8849
(2) 在服务器端自装Jprofiler
在服务器端(Linux)装jprofiler_linux_6_2.rpm:: rpm -ivh jprofiler_linux_6_2.rpm
查找安装后所在目录: rpm -ql jprofiler-6.2-1.i386
配置环境变量:vim /etc/profile
添加以下参数:
JPROFILE_HOME=/opt/jprofiler6
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JPROFILE_HOME/bin/linux-x86
export JPROFILE_HOME
export LD_LIBRARY_PATH
启动resin服务器:/opt/resin-page/bin/httpd.sh -agentpath:/opt/jprofiler6/bin/linux-x64/libjprofilerti.so=port=8849