java内存泄露定位与分析.docx
- 文档编号:6686420
- 上传时间:2023-01-09
- 格式:DOCX
- 页数:16
- 大小:563.48KB
java内存泄露定位与分析.docx
《java内存泄露定位与分析.docx》由会员分享,可在线阅读,更多相关《java内存泄露定位与分析.docx(16页珍藏版)》请在冰豆网上搜索。
java内存泄露定位与分析
使用IBM性能分析工具解决生产环境中的性能问题(javacore)
上一篇 / 下一篇 2012-06-0114:
14:
01/个人分类:
javacore
查看(655) / 评论(0) / 评分( 0 / 0 )
/cn/java/j-lo-javacore/index.html
序言
企业级应用系统软件通常有着对并发数和响应时间的要求,这就要求大量的用户能在高响应时间内完成业务操作。
这两个性能指标往往决定着一个应用系统软件能否成功上线,而这也决定了一个项目最终能否验收成功,能否得到客户认同,能否继续在一个行业发展壮大下去。
由此可见性能对于一个应用系统的重要性,当然这似乎也成了软件行业的不可言说的痛——绝大多数的应用系统在上线之前,项目组成员都要经历一个脱胎换骨的过程。
生产环境的建立包含众多方面,如存储规划、操作系统参数调整、数据库调优、应用系统调优等等。
这几方面互相影响,只有经过不断的调整优化,才能达到资源的最大利用率,满足客户对系统吞吐量和响应时间的要求。
在无数次的实践经验中,很多软件专家能够达成一致的是:
应用系统本身的优化是至关重要的,否则即使有再大的内存,也会被消耗殆尽,尤其是产生OOM(OutOfMemory)的错误的时候,它会贪婪地吃掉你的内存空间,直到系统宕机。
内存泄露—难啃的骨头
产生OOM的原因有很多种,大体上可以简单地分为两种情况,一种就是物理内存确实有限,发生这种情况时,我们很容易找到原因,但是它一般不会发生在实际的生产环境中。
因为生产环境往往有足以满足应用系统要求的配置,这在项目最初就是根据系统要求进行购置的。
另外一种引起OOM的原因就是应用系统本身对资源的的不恰当使用、配置,引起内存使用持续增加,最终导致JVMHeapMemory被耗尽,如没有正确释放JDBC的ConnectionPool中的对象,使用Cache时没有限制Cache的大小等等。
本文并不针对各种情况做讨论,而是以一个项目案例为背景,探索解决这类问题的方式方法,并总结一些最佳实践,供广大开发工程师借鉴参考。
项目背景介绍
项目背景:
1.内网用户500人,需要同时在线进行业务操作(中午休息一小时,晚6点下班)。
2.生产环境采用传统的主从式,未做Cluster,提供HA高可用性。
3.服务器为AIXP570,8U,16G,但是只有一半的资源,即4U,8G供新系统使用。
项目三月初上线,此前笔者与架构师曾去客户现场简单部署过一两次,主要是软件的安装,应用的部署,测一下应用是不是能够跑起来,算作是上线前的准备工作。
应用上线(试运行)当天,项目组全体入住客户现场,看着用户登录数不断攀升,大家心里都没有底,高峰时候到了440,系统开始有点反应变慢,不过还是扛下来了,最后归结为目前的资源有限,等把另一半资源划过来,就肯定没问题了。
(须知增加资源,调优的工作大部分都要重新做一遍,系统级、数据库级等等,这也是后面为什么建议如果资源可用,最好一步到位的原因。
)为了临时解决资源有限的问题,通过和客户协商,决定中午12点半和晚上11点通过系统调度重启一次应用服务器,这样,就达到了相隔几个小时,手动清理内存的目的。
项目在试运行阶段,仍旧有新的子应用开始投入联调,同时客户每天都会提出这样那样的需求变更,如果要的很急的话,就要随时修改,隔天修正使用。
修改后没有充分的时间进行回归测试,新部署的代码难免会有这样那样的问题,遇到过几次这种情况,最后不得不在业务系统使用的时候,对应用系统进行重新启动,于是就会出现业务终止引起的数据不一致,还要对这些数据进行修正维护,加大了工作量。
期间,应用在运行过程中有几次异常缓慢的情形,由于业务不能中断太久,需要迅速恢复系统投入使用,所以往往是重启一下应用服务器来释放内存。
事后检查日志,才发现日志中赫然记录有OOM的错误,这才引起了项目经理的注意,要求架构师对该问题进行进一步研究确认。
但是几个月过去,问题依旧出现,于是通过客户和公司的协调,请来几位专家,包括操作系统专家、数据库专家,大部分的专家在巡检之后,给出的结论是:
大部分需要调整的参数都已经调整过了,还是要从应用系统本身找原因,看来还是要靠我们自己来解决了。
(最终的结果也证明,如此诡异隐蔽的OOM问题是很难一眼就能发现的,工具的作用不可忽视。
)
我们通过对底层封装的框架代码,主要是DAO层与数据库交互的统一接口,增加了log处理以抓取所有执行时间超过10秒钟的 SQL 语句,记录到应用系统日志中备查。
同时通过数据库监控辅助工具给出的建议,对所有超标的SQL通过建立index,或者修正数据结构(主要是通过建立冗余字段来避免多表关联查询)来进行优化。
这样过了几天后,已经基本上不存在执行时间超过10秒的SQL语句,可以说我们对应用层的优化已经达标了。
但是,宕机的问题并没有彻底解决,陆续发生了几次,通过短暂的控制台监控,发现都有线程等待的现象发生,还有两三次产生了几个G大小的heapdump文件,同时伴随有javacore文件产生。
因为每次宕机的时候都需要紧急处理,不允许长时间监控,只能保留应用服务器日志和产生的heapdump文件,做进一步的研究。
通过日志检查,我们发现几次宕机时都发生在相同的某两个业务点上,但是多次对处理该业务功能的代码进行检查分析,仍旧没有找到原因。
看来只能寄希望于宕机产生的heapdump和javacore了,于是开始重点对OOM产生的这些文件进行分析。
IBM 分析工具的使用
这里,我们简单介绍一下heapdump文件和javacore文件。
heapdump文件是一种镜像文件,是指定时刻的 Java 堆栈的快照。
应用程序在发生内存泄露的错误时,往往会生成heapdump文件,同时伴随有javacore文件生成,javacore包含JVM和应用程序相关的在特定时刻的一些诊断信息,如操作系统,内存,应用程序环境,线程等的信息。
如本文案例中分析的下图中的heapdump..134015.430370.phd和javacore..134015.430370.txt。
由于笔者之前并没有这方面的分析经验,觉得heapdump文件很大,就想当然拿它开刀了。
首先是寻找工具,类似的工具有多种,笔者最后选择了IBM的HeapAnalyzer(.com/tech/heapanalyzer)。
通过对heapdump文件的解析,HeapAnalyzer可以分析出哪些对象占用了太多的堆栈空间,从而发现导致内存泄露或者可能引起内存泄露的对象。
它的使用很简单,可以从它的readme文档中找到,这里我们简单举个例子如下:
#/usr/java50/bin/java–Xmx2000m–jarha36.jarheapdump..134015.430370.phd
通常我们需要使用较大的heapsize来启动HeapAnalyzer,因为通过HeapAnalyzer打开过大的heapdump文件时,也可能会因为heapsize不够而产生OOM的错误。
开始的时候,笔者从服务器上将heapdump文件通过ftp下载下来,然后试图通过本机window环境进行分析。
笔者使用的电脑是2G内存,启动HeapAnalyzer时指定的是1536M,但是几次都是到90%多的时候进度条就停止前进了。
迫不得已同时也是为了能达到环境一致的效果,笔者将HeapAnalyzer上传到生产环境,直接在生产环境下打开heapdump文件。
个人感觉HeapAnalyzer给出的分析结果可读性不强,而且不能准确定位问题,从分析结果看大致是因为加载的对象过多,但是是哪个模块导致的问题就跟踪不到了。
笔者开始寻找HeapAnalyzer分析结果相关的资料,试图从这个可读性不强的结果中找到蛛丝马迹,可是许久没有进展,再一次陷入了困境。
在多次研究heapdump文件无果的情况下,笔者开始逐渐将注意力转移到javacore文件上,虽然它比较小,说不定内藏玄机呢。
通过多方搜寻,找到了IBMThreadandMonitorDumpAnalyzerforJava(以下简称jca)——Atoolthatallowsidentificationofhangs,deadlocks,resourcecontention,andbottlenecksinJavathreads。
通过它自身的这段描述来看,这正是笔者所需要的好工具。
这个工具的使用和HeapAnalyzer一样,非常容易,同样提供了详细的readme文档,这里也简单举例如下:
#/usr/java50/bin/java-Xmx1000m-jarjca37.jar
图2.通过xManager工具登录到AIX服务器上打开jca的效果图
笔者直接在生产环境下直接通过它对产生的javacore文件进行分析,令人惊喜的是,其分析结果非常明了,笔者心头的疑云在对结果进行进一步分析核实后也渐渐散去。
图3.jca对的分析结果——第一部分
从图中,我们可以清楚地看到引发错误的原因——Thefailurewascausedbecausetheclassloaderlimitwasexceeded。
同时我们还能看出当前生产环境使用的JRE版本是:
J2RE5.0IBMJ92.3AIXppc-32buildj9vmap1,这个SR4的版本有个问题,我们可以从分析结果图的第二部分的NOTE小节清楚地看到:
图4.jca对的分析结果——第二部分
NOTE:
OnlyforJava5.0ServiceRefresh4(builddate:
February1st,2007)andolder.Whenyouusedelegatedclassloaders,theJVMcancreatealargenumberofClassLoaderobjects.OnIBMJava5.0ServiceRefresh4andolder,thenumberofclassloadersthatarepermittedislimitedto8192bydefaultandanOutOfMemoryErrorexceptionisthrownwhenthislimitisexceeded.Usethe-Xmxclparametertoincreasethenumberofclassloadersallowedtoavoidthisproblem,forexampleto25000,bysetting-Xmxcl25000,untiltheproblemisresolved.
原来,OOM竟然是因为这个原因产生的。
那么到底是哪里加载的对象超过了这个8192的限制呢?
接下来笔者结合分析结果和应用系统log进行了进一步的分析,更加验证了JCA分析结果的正确性。
在分析结果中可以看到CurrentThread,就是对应引起OOM的应用服务器的线程,使用该线程的名称作为关键字在我们的log里进行搜索,迅速定位到了业务点——这是一个定时的调度,其功能是按固定时间间隔以DBLink的方式从外部应用的数据库系统中抽取数据,通过应用层逻辑转换后保存到内网数据库系统中。
应用层的逻辑是对所有的业务数据进行循环,对每条业务数据进行到POJO的一一对照转换,这意味这一条业务数据需要10几个POJO进行组合对照,也就是说,如果外网在指定的时间间隔内数据量稍大,就会加载大量的对象,使JVM的classloaders瞬间超过8192的限制,而产生OOM的错误,从而使内存不断被消耗,直至宕机。
jca还提供了对垃圾回收和线程状态的详细分析数据,可以参考如下两图:
图5.jca对垃圾回收状态的分析数据
图6.jca对垃圾线程状态的分析数据
问题核实后,如何进行解决呢?
分析结果中也给出了相应的建议,从图4的Recommended小节我们可以看到:
Recommended-Xmxclsetting(onlyforIBMJava5.0,uptoandincludingServiceRefresh4(builddate:
February1st,2007)):
10,649orgreater。
为了考虑到对既有旧系统不产生影响,我们没有对JRE进行升级,而是采用了分析结果给出的建议,在应用服务器启动后设置-Xmxcl参数为25000,为验证我们的修改是不是成功解决掉了OOM宕机的问题,笔者取消了对应用服务器的自动重启调度,通过一段时间的监控发现,系统运行良好,一直没有再出现OOM而宕机的问题,问题得以最终解决。
总结
1.整体规划
系统上线前要做好充分的准备工作,这一点很重要,上线计划可不是拍拍脑门就能想出来的,需要很多项目成员的参与。
a.项目实施计划书:
要包括预计实施目标,双方的实施负责人(包括backup),协调哪些资源应该找哪些人,详细的阶段性计划等等。
b.实施小组:
指定具有实施经验的架构师,系统工程师,开发组长组成实施小组,负责生产环境的搭建及调整。
成员构成上一定要包含项目组内的稳定核心人员,以保证能在特殊情况需要时做backup。
核心人员的流动会引起项目风险,尤其是在上线初期的不稳定阶段。
c.对可规划使用的资源一定要充分利用,这一点应包含在项目实施计划书内,尽量做到资源一步到位。
因为本项目案例中,旧有系统和新系统并行运行,可用的资源在不断发生变化,期间为解决性能问题,曾尝试将资源分配向新系统倾斜,以尝试是否能解决问题,这在一定程度上也会影响实施的进度。
d.需要对核心的配置进行确认,如JRE版本,数据库版本等。
如果我们在生产环境建立的初期,就把JRE升级到最新的版本,可能就避免了这次6个月宕机的煎熬。
2.压力测试
大部分具有并发需求的应用系统上线前,都要经过开发团队的压力测试,这样在试运行阶段才能心里有底。
本项目案例中,由于工期过于紧张,加之其他因素,导致压力测试不够充分,虽然OOM的问题并不是因为并发数过多产生,但是这一环节是不可忽视的。
3.攻坚团队
一个拥有高度的执行力的团队一定是一个责任分明的团队。
在应对突发、严重、紧急情况时,要有专门的攻坚团队来解决这类问题,这个团队应该包括项目组的核心开发人员,有较强的动手能力和研究能力,能够变通解决问题,思路开阔。
他们的作用是至关重要的,往往可能决定项目的成败。
4.信心很重要
每一次攻坚内存泄露的问题都是一次探险之旅,需要有清醒的意识和头脑,更要凝结你的意志和信心,因为这是一个艰苦的过程。
本文案例由发生至解决有半年之久,当笔者转向分析javacore文件后,迅速定位了问题,终于柳暗花明,这种喜悦是难以言表的。
5.纸上得来终觉浅,绝知此事要躬行。
这一条不多说了,有所行,才有所得。
java内存泄漏的定位与分析
分类:
服务器性能监控 jvm性能调优2013-01-2416:
32 4356人阅读 评论(3) 收藏 举报
1、为什么会发生内存泄漏
java 如何检测内在泄漏呢?
我们需要一些工具进行检测,并发现内存泄漏问题,不然很容易发生down机问题。
编写java程序最为方便的地方就是我们不需要管理内存的分配和释放,一切由jvm来进行处理,当java对象不再被应用时,等到堆内存不够用时,jvm会进行垃圾回收,清除这些对象占用的堆内存空间,如果对象一直被应用,jvm无法对其进行回收,创建新的对象时,无法从Heap中获取足够的内存分配给对象,这时候就会导致内存溢出。
而出现内存泄露的地方,一般是不断的往容器中存放对象,而容器没有相应的大小限制或清除机制。
容易导致内存溢出。
当服务器应用占用了过多内存的时候,如何快速定位问题呢?
现在,EclipseMAT的出现使这个问题变得非常简单。
EclipseMAT是著名的SAP公司贡献的一个工具,可以在Eclipse网站下载到它,完全免费的。
要定位问题,首先你需要获取服务器jvm某刻内存快照。
jdk自带的jmap可以获取内存某一时刻的快照,导出为dmp文件后,就可以用EclipseMAT来分析了,找出是那个对象使用内存过多。
2、内存泄漏的现象:
常常地,程序内存泄漏的最初迹象发生在出错之后,在你的程序中得到一个OutOfMemoryError。
这种典型的情况发生在产品环境中,而在那里,你希望内存泄漏尽可能的少,调试的可能性也达到最小。
也许你的测试环境和产品的系统环境不尽相同,导致泄露的只会在产品中暴露。
这种情况下,你需要一个低负荷的工具来监听和寻找内存泄漏。
同时,你还需要把这个工具同你的系统联系起来,而不需要重新启动他或者机械化你的代码。
也许更重要的是,当你做分析的时候,你需要能够同工具分离而使得系统不会受到干扰。
一个OutOfMemoryError常常是内存泄漏的一个标志,有可能应用程序的确用了太多的内存;这个时候,你既不能增加JVM的堆的数量,也不能改变你的程序而使得他减少内存使用。
但是,在大多数情况下,一个OutOfMemoryError是内存泄漏的标志。
一个解决办法就是继续监听GC的活动,看看随时间的流逝,内存使用量是否会增加,如果有,程序中一定存在内存泄漏。
3、发现内存泄漏
1.jstat-gcpid
可以显示gc的信息,查看gc的次数,及时间。
其中最后五项,分别是younggc的次数,younggc的时间,fullgc的次数,fullgc的时间,gc的总时间。
2.jstat-gccapacitypid
可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,
如:
PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,
PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。
其他的可以根据这个类推, OC是old内纯的占用量。
3.jstat-gcutilpid
统计gc信息统计。
4.jstat-gcnewpid
年轻代对象的信息。
5.jstat-gcnewcapacitypid
年轻代对象的信息及其占用量。
6.jstat-gcoldpid
old代对象的信息。
7.stat-gcoldcapacitypid
old代对象的信息及其占用量。
8.jstat-gcpermcapacitypid
perm对象的信息及其占用量。
9.jstat-classpid
显示加载class的数量,及所占空间等信息。
10.jstat-compilerpid
显示VM实时编译的数量等信息。
11.stat-printcompilationpid
当前VM执行的信息。
一些术语的中文解释:
S0C:
年轻代中第一个survivor(幸存区)的容量 (字节)
S1C:
年轻代中第二个survivor(幸存区)的容量 (字节)
S0U:
年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U:
年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC:
年轻代中Eden(伊甸园)的容量 (字节)
EU:
年轻代中Eden(伊甸园)目前已使用空间 (字节)
OC:
Old代的容量 (字节)
OU:
Old代目前已使用空间 (字节)
PC:
Perm(持久代)的容量 (字节)
PU:
Perm(持久代)目前已使用空间 (字节)
YGC:
从应用程序启动到采样时年轻代中gc次数
YGCT:
从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:
从应用程序启动到采样时old代(全gc)gc次数
FGCT:
从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:
从应用程序启动到采样时gc用的总时间(s)
NGCMN:
年轻代(young)中初始化(最小)的大小 (字节)
NGCMX:
年轻代(young)的最大容量 (字节)
NGC:
年轻代(young)中当前的容量 (字节)
OGCMN:
old代中初始化(最小)的大小 (字节)
OGCMX:
old代的最大容量 (字节)
OGC:
old代当前新生成的容量 (字节)
PGCMN:
perm代中初始化(最小)的大小 (字节)
PGCMX:
perm代的最大容量 (字节)
PGC:
perm代当前新生成的容量 (字节)
S0:
年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1:
年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E:
年轻代中Eden(伊甸园)已使用的占当前容量百分比
O:
old代已使用的占当前容量百分比
P:
perm代已使用的占当前容量百分比
S0CMX:
年轻代中第一个survivor(幸存区)的最大容量 (字节)
S1CMX :
年轻代中第二个survivor(幸存区)的最大容量 (字节)
ECMX:
年轻代中Eden(伊甸园)的最大容量 (字节)
DSS:
当前需要survivor(幸存区)的容量 (字节)(Eden区已满)
TT:
持有次数限制
MTT :
最大持有次数限制
如果定位内存泄漏问题我一般使用如下命令:
Jstat -gcutil15469250070
[root@sssslogs]#jstat-gcutil15469 1000300
S0S1EOPYGCYGCTFGCFGCTGCT
0.001.4626.544.6130.14350.87200.0000.872
0.001.4646.
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- java 内存 泄露 定位 分析