近段时间,我们项目中用到的WebSphere
应用服务器(WAS)
,但在客户的production
环境下极不稳定,经常宕机。给客户造成非常不好的影响,同时,也给项目组很大
压力。为此,我们花了近一个月时间对其诊断,现在基本上稳定了,需要继续观察一段时间。现在我主要将工作做一个阶段性的总结。
我们的产品环境是:WAS6.0
+DB2
8.1
+AIX5.3+RS/6000
。在该产品环境下,出现的问题非常多,现象如下:
WAS经常不稳定、宕
机几乎一天一次,经常报告OutOfMemory
(
内存泄漏吗?NO)。
DB2
连接数过大,有时把DB2
撑死,有时也把AIX
撑死。
AIX虚拟内存报错、分页报错
、IO
也报错、还有很多其它莫名奇妙的错。
总是,每次问题发生的现象和理论上的总是不一致,导致我们不知道从何入手,也无从检测自己的优化参数。咨询过多次IBM
技术支持,只解决了某些局部问题。
虽然问题依然存在,但我想,解决问题的思路、特别是理论基础,还是有一些规律和原则。
对于WAS
这块,我近段时间的主要时间集中在以下几个方面(
时间顺序)
:
1
、Java
性能监测工具:Jprofiler
,也用到Jprobe
。后来发现Jprofiler
在AIX
下几乎不可用。
2
、IBM Java
虚拟机和WAS
技术细节,特别是IBM JVM的GC
原理,我发现它和sun
、bea
的差别很大。
3
、IBM
的heap
分析器Heap
Analyzer、GCCollector
。这两个事后监测工具非常实用,特别是我们的产品运行环境,非测试环境。
4、某些Application
的怀疑和诊断。
5
、AIX
诊断,我几乎没有这个能力,只能常规监测一下,需另请高人。
我打算将本文分成以下几个部分总结:
JVM
原理、IBM JVM
的GC
策略和调优。
Jprofiler
和IBM
工具的实际体会
WAS的诊断体会和AIX
调优
下面开始主题吧,可能比较零碎,另外,开始的理论篇基本上看书都可以,我只是总结一下,再添加一些自己的理解。
以下是我参考的最重要的两本电子书和一些网站:
《Inside Java Vrtual
Machine
》:
半部分有约四章我认为非常
棒,其它章节可能意义不大。
《The Java Virtual Machine Specification,
2nd
》:前半部分有两三章很不错,不过可以对照上一本书看。
sun
的hotspot
虚拟机技术:http://java.sun.com/javase/technologies/hotspot/
BEA
的JRockit
虚拟机技术:http://edocs.bea.com/jrockit/geninfo/genintro/index.html
JVM技术文档入口,虚拟机理论,内存泄漏诊断等的索引页。
IBM诊断资料:http://www-128.ibm.com/developerworks/java/jdk/diagnosis/
上面有一个500
多页的pdf
文档,对IBM JVM技术和诊断讲解很深入。
我不得不提的是,在查资料这块,BEA 和Sun 都有很好的官方文档和论坛支持,并且官方文档导航非常好。虽然IBM 的诊断资料也不少,但需要搜索,其搜索是很痛苦的。而且,IBM 官方论坛很差。如果用IBM的产品出问题,切记:找IBM 技术支持,千万不要蒙着头搞!反正它们的产品很少免费。说实话,它们的技术支持还是挺负责的,一般会为你推荐很多support资料,而该资料往往都在developerworks 网站上,属于support那个频道,但你就是搜不着。
Java
虚拟机规范概要
研究Java
虚拟机,首先要了解Sun
的Java
虚拟机规范。现在,该实现版本很多,如比较有名的Sun、IBM
、BEA
、Apple、HP
、MS
、Apache
Harmony。它们都实现了JVM
规范,但有各自扩展。譬如,针对IBM虚拟机的堆碎片导致OutOfMemory
(OOM),在Sun
的虚拟机上就不会发生。Sun
的JVM有maxPermSize
的概念,IBM就没有,如果你设置这个参数,虚拟机根本就启动不了。
比较有意思的是,学Java
,就一定要了解各种规范,这和MS
的风格很不一样。Sun总是在定义一些规范,实现都留给各厂商。我们除了理解规范本身外,一定要理解规范和实现之间的关系,譬如JDBC规范和JDBC
驱动的关系,它们是怎么组合到一起的。要是你用过php
的xml
解析库,或db函数,就会体会深刻,它们可没有什么规范可言,所以每个数据库厂商的db
函数用法都不一样。我推荐大家研读一下HSQLDB的jdbc
和Tomcat的servlet
相关实现,因为我认为它们还是比较好懂的。
JVM规范只是定义一个虚拟机该做什么,但它并没有要求你该怎么做。例如我们最常见的Servlet
规范,在该规范中,有HttpServletRequest
、HttpServletResponse
,HttpSession
等接口,但它们的实现都留给了各个容器厂商。遗憾的是,规范留下的空白,会把我们这些开发人员给整惨了:容器间移植有时候就是恶梦。譬如J2EE
并没有SSO
规范,但它很重要,我以前专门针对它做过WebSphere
AppServer
和Weblogic
AppServer
的SSO
项目,差别还是不小,不过还是有点共通,那就是都遵循JAAS规范。
JVM
的结构
从功能上分,Java
虚拟机主要由六个部分组成,可以分成三类:
第一类:JVM API
:就是我们最常用的Java
API
,它是开发人员和Java交互的入口,它主要是JAVA_HOME/jre
/lib
下的运行时类库rt.jar
和编译相关的tools.jar
第二类:JVM
内部组件
类装载器
(ClassLoader
)
:将Byte
Array
的 .class
文件装载、链接和初始化。
内存管理
(Memory
Managent
)
:为对象分配内存,以及释放内存。后者就是垃圾回收Garbage
Collector(GC
)。由于JVM最复杂的、最影响性能的就是GC
,所以内存管理一般就指垃圾回收。
诊断接口
(Diagostics
Interface)
:这主要体现在JVMTI(jdk1.4
下的JVMPI
和JVMDI),它主要用来诊断程序的问题和性能,一般提供给工具
厂商实现。如eclispe
IDE
下的debug功能,Jprofiler
性能调优工具。
类解释器
(Interpreter)
:解释装载进虚拟机的class对象,包括JIT
等特性相关。
第三类:平台相关接口(Platform Interface) :主要为了跨操作系统平台重用JVM 代码,不过,它和我们开发人员关系不大。
在以上六个组件中,我们开发人员最关心的是ClassLoader
和GC
,用Java
做系统框架、容器和它们密切相关。做业务系统时一些基础代码也和它们打交道,譬如最常用的Class.forName
(),Thread.currentThread.getContextClassLoader
()。我们仔细想想,为什么是上面两个问题?因为,它和我们class的整个生命周期最为相关:怎么将一个class
和相关class加载进来,class
实例什么时候创建,什么时候被销毁?
所以,下面的部分我们要专门讨论这些问题。
ClassLoader
JVM主要有三类ClassLoader
:Bootstrap
、Extention
、Application
,该三类ClassLoader
从上到下是分级(hierarchy)
结构,遵循代理模型(Delegation
Model)。
Tip
:大家可以看看sun.misc.Launcher
的源码,Bootstrap和Extention
就在该文件里。该src
可以在sun
的网站上下载该压缩包,约60M
(jdk-1_5_0-src-scsl.zip),它不在jdk
自带的那个src.zip
里。
Bootstrap ClassLoader :也称为primordial(root) class loader 。主要是负责装载jre /lib 下的jar文件,当然,你也可以通过-Xbootclasspath 参数定义。该ClassLoader 不能被Java 代码实例化,因为它是JVM本身的一部分。
Extention ClassLoader :该ClassLoader 是Bootstrap classLoader 的子class loader。它主要负责加载jre /lib/ext/下的所有jar 文件。只要jar包放置这个位置,就会被虚拟机加载。一个常见的、类似的问题是,你将mysql 的低版本驱动不小心放置在这儿,但你的Web 应用程序的lib下有一个新的jdbc 驱动,但怎么都报错,譬如不支持JDBC2.0的DataSource ,这时你就要当心你的新jdbc 可能并没有被加载。这就是ClassLoader 的delegate 现象。常见的有log4j、common-log 、dbcp 会出现问题,因为它们很容易被人塞到这个ext 目录,或是Tomcat下的common/lib 目录。
Application
ClassLoader
:也称为System
ClassLoaer
。它负责加载CLASSPATH环境变量下的classes
。缺省情况下,它是用户创建的任何ClassLoader
的父ClassLoader
,我们创建的standalone
应用的main class缺省情况下也是由它加载(
通过Thread.currentThread
().getContextClassLoader
()查看)。
我们实际开发中,用ClassLoader
更多时候是用其加载classpath
下的资源,特别是配置文件,如ClassLoader.getResource
(),比FileInputStream
直接。
ClassLoader
是一种分级(hierarchy
)
的代理(delegation
)模型。
Delegation
:其实是Parent
Delegation
,当需要加载一个class时,当前线程的ClassLoader
首先会将请求代理到其父classLoader
,递归向上,如果该class已经被父classLoader
加载,那么直接拿来用,譬如典型的ArrayList
,它最终由BootstrapClassLoader
加载。并且,每个ClassLoader
只有一个父ClassLoader
。
Class查找的位置和顺序依次是:Cache
、parent
、self。
Hierarchy
:上面的delegation
已经暗示了一种分级结构,同时它也说明:一个ClassLoader
只能看到被它自己加载的classes,或是看到其父(parent) ClassLoader
或祖先(ancestor)
ClassLoader
加载的Classes。
在一个单虚拟机环境下,标识一个类有两个因素:class
的全路径名、该类的ClassLoader
。
我碰到的一个典型的例子是:在做WAS
的SSO
开发时,由于我们的类是由WAS
在启动时加载,该ClassLoader
比下面的部署的Applicaton
的ClassLoader
的级别高。所以,在我们自己的类中没法用到应用程序的连接池,必须自建。
代理模型是Java
安全模型的保证。譬如,我们自己写一个String.java
,并且编译、package
到自己的java.lang
包下。按照代理模型,当前线程的ClassLoader
会将其代理到父ClassLoader
,父ClassLoader
(
最终会是Bootstrap)会找到rt.jar
下的String.class
,也就是说我们的String.class
不会捣乱。
自定义ClassLoader
我们前面说过,自定义ClassLoader
的缺省父ClassLoader
是Application
ClassLoader
。一般的应用开发用不到它,但我们最好理解。因为在内存泄漏查找、应用程序部署出问题时,很多都和它有关。
譬如,内存泄漏是怎么产生的?这就涉及到ClassLoader
和Class的生命周期。我曾经碰到这样一个问题:我们的程序用到了Webwork
和Spring
框架,当部署到Tomcat
下时没有任何问题,但部署到WAS下,报告找不到Webwork
的xml的DTD
文件,而且Spring
的日志也总是失效。Why?因为解析xml
dtd
时,用的是IBM的Xerces
,不是我们的。而Spring日志问题是因为应用程序用的是WAS
的Common-log.jar
,而不是我们的。将应用的ClassLoader
从默认的Parent-First
,改成Parent-Last就可以解决,不过我们项目中用到其它库,又发生了其它问题。
一般来说,用到自定义ClassLoader
有三种情况:
1
、应用框架可以自己控制Classes
的目录,并且自动部署。
我读过Jive
公司的Wildfire(
著名的即时通讯服务器),它自己有一套应用框架,非常灵活,遵循该框架插件规范的的
第三方的plug-in放置在指定目录可以自动部署,实现某些扩展功能,如文件传输、语音聊天。
2
、区分用户代码
这被广泛应用在Servlet
容器和类似容器,譬如EJB
Container设计中,大家看到Tomcat
下有common、server
、share
三个目录吧(ClassLoader
顺序从左到有)
,另外也有用户应用的WEB-INF目录,它是我们自己开发的。
3
、允许Classes
卸载
如果没有自定义的ClassLoader
,那么我们自己应用中的classes永远都不能被卸载,因为这些类被Application ClassLoader
加载后cache
起来了,我们的classes一直对该ClassLoader
有引用,而该系统级的ClassLoader
永远都不会被卸载,除非JVM
shutdown了。JSP
和Servlet
的动态部署就用到这个特性。
Note: 还有JVM 运行时(Runtime) 架构,ClassLoader 加载class 过程没有总结,这两部分我觉得太重要了,但内容太多,写不完啊。
??? 这部分内容,《Inside Java Virtual Machine 》讲解非常清楚,BEA的官方网站这部分也非常不错,要理解深刻,我建议结合JProfiler 工具,非常直观。
?
续写这篇文章,已经过去一个半月了。直到现在,系统一直运行平稳。
先说说我接手这项工作的经历吧:该项目大部分是06
年10
月就部署在客户那边了,到07年3
月份,WAS
宕机问题实在无法忍受,我才加入进来,前半年有另外一位同事断断续续处理,但对问题一直都无可奈何,而且项目负责人也没有引起足够的重视。可想而知,最后付出的代价是非常惨重的。在这近半年的时间内,服务器宕机63次。每次宕机时,WAS
的JVM
会dump出一个heapdump.phd
文件(heap快照)
,然后JVM
就死掉了,当然,此时WAS也停止了响应。一般我们的做法是重启,最后是干脆AIX
每天晚上定时重启。有时候一天还死多次。大家见附件的截图(all-GC.png)。这是我接手后,用IBM
的分析工具得到的截图。对截图的分析,留给后面对应的部分吧。
服务器不稳定、宕机问题,拖延到最后,客户愤怒了,公司高层也害怕了,部门还专门成立了八人攻关组。当然了,我当时的压力也非常大,因为我是技术负责人,也就是实实在在干活、想主意的。
服务器诊断那段时间,从前到后,我们也是沿着一条线走下来,虽然最后发现很多路都走不通。现在就按这个思路,也就是时间先后一步步叙述吧。我想,大家如果也碰到类似应用服务器诊断,应该思路差不多。
术语说明:
IBM Websphere Application Server
:WAS
,WebSphere本身是一个平台,产品家族
OutOfMemoryError
:OOM
,内存泄漏,内存溢出
Gabage
Collection
:GC
,自动垃圾回收
Content
Management System
:CMS
,就是给新闻类门户网站编辑们用的系统
我们诊断大体上经历了以下几个阶段:
1
、按Job
调度线程池引起内存泄漏诊断:因为很多次OOM
是发生在某个特定时候,譬如14:30
、22
:40左右。
2
、按应用程序引起内存泄漏诊断:用JProfiler
等工具探测:因为总是发生OOM。
3
、分离WAS
怀疑有OOM
的应用:因为每个WAS应用太多,20
来个,混一起没法定位。
4、用IBM
官方heap
、GC分析工具。以及和IBM
技术支持联系。WAS
、AIX参数优化。
5
、隔离出WAS
超级恶魔程序:一个CMS
产品。
6、WAS
、AIX
参数优化、设置。
我们走到第5 步时,才出现效果。计算一下,那时已经过去一个月了。服务器宕机、系统不稳定,在这个验收的时候,客户已经忍无可忍,以致后来的每一次行动都得胆战心惊得去做。
一、按Job
调度线程池导致内存泄漏诊断
因为从我们WAS
的日志(
默认是native_stderr.log)
来看,最近半年的宕机时间都有一个明显时间规律。见附件截图Job1-1.png。
我想,做过Java
服务器性能调优的朋友,都知道在Web
容器里面启线程池是个不太好的做法,因为Web容器本身有一个线程池,譬如Servlet
线程池(Tomcat默认起25
个),而自启的线程池很容易导致Servlet线程管理混乱,最终导致GC
问题。我们的现象似乎和那很符合。如果我们沿着这个思路做下去,具体怎样实施呢?
我们的WAS
上部署了20
个左右的Web
应用,譬如Lucene全文检索、B2B
行业数据同步等,都是通过Quartz的Job
调度做的,当然还有很多其它调度。当时,由我负责,通知相关负责人,将定时调度暂时去掉。观察了几天,后来发现问题依然存在,不过时间有点随机了。
不过,最后还是发现OOM
不是由Job
调度引起的。
也就是说,我们这个方案是失败的。而且,我们的很多想法都是臆测的,没有可靠的根据,也没有方向,再加上我是第一次处理这种问题,这导致后来查找问题的艰难。但是,仔细想想,我们又能拿什么做依据呢?出现OOM错误,我想大多数人想到的,除了JVM
参数设置,就是内存泄漏。其实,OOM发生有很多种情况,在IBM
、Sun
、BEA等不同虚拟机上,因为GC
机制不一样,所以原因一般都不同,容易定位难度也不一样。下文会谈到。
于是,我们干脆釜底抽薪分析问题吧:用JProfiler
检测。
二、按应用程序导致内存泄漏诊断,JProfiler
检测
如果遇到OOM
问题,我想大家都会想到内存检测工具,现在最可靠的还是下面三种分析工具:Borland
的Optimizeit
Suite
,Quest的JProbe
,ej-technologies
的JProfiler。但面临三个问题:
1
、三个都是商业产品,公司暂时没有买,必须自己下载,而且要找序列号。
2
、工具必须支持AIX5.3
+JDK1.42
+WAS6.0,不是Windows
平台。
3
、工具必须在客户真实环境下部署,对客户的业务不能有冲击,也就是说部署测试工具前,必须做个大量测试,对工具非常熟练,遇到意外可以立即恢复现场。
Note:项目上线后,而不是测试或试运行阶段遇到此类问题,非常考验人;另外一个,就是性能和可伸缩性问题,很可能把整个项目给毁了。
当我决定要这么做后,就立即动手查阅这些工具的官方文档,用emule 下载,最终都下载到了。试用后,最终选择了JProfiler4.03 ,比起其它工具,它界面美观、清晰、功能强大、集成度高(Heap,Memory,CPU,Thread都统一了) 。另外,JProbe没有AIX 版本,这也是放弃它的一个原因。
JVM
的Profiler
原理,都是通过JVM
内置的的标准C
语言Profiler
接口收集数据,然后通过Profiler
工具的客户端展现。也就是说各厂商的Profiler工具,都有两个部分,一个部分是Profiler Agent
,和JVM绑定,负责收集JVM
内部数据,譬如方法调用次数、耗费时间等;另外一个部分就是Profiler
front-end。通过Profiler
工具的自定义local或remote
协议传输到front-end
,其实,我们最常用的JavaIDE的debug
功能就是在此基础上的(JPDA)。(JProfiler
的截图http://www.ej-technologies.com/products/jprofiler/screenshots.html
)。
下面是Sun
官方文档:
JDK1.42
及以前是JVM PI
:http://java.sun.com/j2se/1.4.2/docs/guide/jvmpi/jvmpi.html
JDK1.5
是JVM TI
:http://java.sun.com/j2se/1.5.0/docs/guide/jvmti/jvmti.html
具体到JProfiler
的配置上,专门针对JDK1.4
和1.5的JVM
配置差别很大。
我用的JProfiler
是4.31
版本,透露给大家一个万能序列号吧(这东西不太好找)
,对各版本应该都支持。深入了解Java,这类工具是不可少的:
Name: License for You
Lincese Code:
A-G667#42616F-10kg1r134fq9m#2217
为了保证真实环境的检测成功,我做了大量的试验,譬如:
1
、Windows
系列的本地、远程测试。
2
、AIX
的远程测试。
3
、Tomcat5.0
、WebLogic8.14
、WebSphere6.02,以及上述两种方式的组合测试,排列组合,场景不下10
个。
当时也参阅了大量JVM
文档,JProfiler
官方几百页英文文档,辅助的JProbe对照。而且也制造过内存泄漏造成的OOM
场景。
当然,要是在几个月前,在客户那边部署的测试环境时,就进行测试该多好啊。
在公司内部,我用JProfiler 测试了我们当时部署的几个应用,没有发现内存泄漏,所以,我们最怀疑的是就是CMS系统。因为出问题的那个WAS 上它占去了90%的负荷(我们有多台AIX 、WAS 服务器)。该CMS超级庞大,感觉著名的赛迪网就用它,当时该CMS 厂商给我们部署都花了快一个月。所以再重新部署一套测试环境也挺困难。另外,CMS提供商不给lisence 。现在测试,客户早就对我们恼火了,当然不怎么配合,这对我们工作的开展就有很大的挑战。
在大致可以确定万无一失的情况下,我们最终决定在客户的真实环境下测试。也就是让JProfiler
的agent
端直接在WAS
的JVM里面启动(北京IDC
),然后远程(大连)监控。
本来该模式在另外几个应用的测试都通过了(因为北京IDC
那边好几台AIX服务器)。但一部署上,客户的一些编辑用CMS
时就感觉超级慢,尽管我们用了JProfiler的最小负载模式。半个小时后,客户实在无法忍受,打电话过来,又把我们部长和经理训了一顿,还要写书面报告。我们被迫马山中止测试,恢复现场。
虽然JProfiler
也支持客户那边的环境,但还是有bug
,至少负载一高就有严重的性能问题,几乎导致系统挂起,这是我当时没有料到的。JProfiler一启动,WAS
的启动时间就由原来的3分钟降到10
分钟,而且系统响应明显变慢,我们具体的环境如下(排列组合恐怕不下20种):
1
、AIX5.3
,Power
PC64
位(不是32位)
2
、WebSphere6.0
3
、IBM JVM1.42
4
、Remote
模式
我后来仔细读了一下JProfiler 的changeLog ,发现对上面的环境支持不够也很正常,因为官方在最近的4.3.x版本下陆续有对IBM JVM 和Websphere6.0 的features和bug fix :http://www.ej-technologies.com/download/jprofiler/changelog.html
进行到这一步,我忽然觉得无计可施了
,此时已经过了三周。
上面的策略,我认为是很正统的处理方法。谁怪我们当初项目上线时没有进行充分的测试呢?其实,这类测试没做也正常,OOM
这类问题谁都无法预测。
到这个时候,我想肯定有人会问我?你知道导致JVM
的OOM
有几种情况吗?在当时,我想到了以下五种:
1、JVM
的heap
最小、最大值没有设,或不合理。
2、JVM
的maxPermSize
没有设置(这个IBM的JVM
没有,一设置JVM
就起不来)。
对于Sun
和BEA
的JVM,以上两种参数设置,可以排除90
%以上的非程序内存溢出导致的OOM。
3
、程序内存泄漏
4
、有的JVM
,当在80%
的CPU时间内,不能GC
出2%
的heap时,也发生OOM
(IBM
的JVM就是,但我没有验证)
5
、JVM
本身内存泄漏(JVM
有bug不是不可能)
现在的难题是,如果是那个可怕的CMS 程序本身有内存溢出,在产品环境下,我们怎么去验证?我们自己开发的10 来个web应用,测试并不是很难,在公司测试都可以。但是,我现在最想解决的,就是CMS 测试的问题。而且,在我们那种环境下,该CMS产品供应商并没有透露成功案例。
其实,最后发现,并不是内存泄漏造成的,因为我们的heap 走势都很平稳。纳闷的是,有1000M 的heap ,每次在heap只被占用400 来M 时就发生OOM,没有任何预兆。大家猜猜,会是什么原因 ?这个问题我到后面相关章节再说吧。
既然我们所有的矛头都指向那个可怕的CMS
系统,现在就是考虑隔离的问题。如果我们验证这个问题是CMS
造成的,那么大部分责任就应该由CMS厂商承担
。
既然CMS
我们不敢移(费劲,而且客户在正式用),那我就移我们开发的10来个web
系统吧。
三、移出除CMS
系统以外的所有应用
说起来容易啊,做呢?
隔离(移动)工作由我负责,具体涉及到10
来个相关负责人。
转移工作,必须处理好很多问题,就说几个印象最深的吧:
1
、某些应用,如Blog
和BBS
,都涉及到文件、图片上传目录和产品本身的环境,如
JDBC连接池、Cache
位置。
2
、目标服务器本身的环境,WAS
安装环境、网络等。
3
、移植时的先后顺序、调度,各应用内部本身的约束关系。
4
、移植后的测试。
当然,还有一个最严峻的问题,客户允许我们这么做吗?对它们目前运行的系统有多大影响?风险如何评估?
这个工作持续了一天,已经完成了80
%的工作,到第二天,客户又恼火了:WAS
又宕机了。
为什么?这确实是WAS
的一个bug
:WAS的后台随便一操作,heap
就会突然上升几百M,导致JVM
内存不够。不过WAS
撑住的话,过半小时后就会降下来,我估计是WAS后台对用户操作状态、文件都缓存到Session
里面。你们可以检查类似这样的一个文件夹:d:\IBM\WebSphere\AppServer\profiles\AppSrv01\wstemp,我不知道为什么WAS不主动去清除它,它偷偷的就上升到几个G
,系统硬盘可能不久就后就会空间不足,WAS莫名迟缓、最后死掉。听过WAS6.0
以前这个目录更夸张。大家见我附件的截图WAS_Console.png那个尖峰。
咋办?经理也已经不敢让我们继续铤而走险了。这个方案最终又以失败告终 。
不过,最后我们还是发现问题出在CMS 上。我们以前把这个问题向CMS 技术支持反映,有大量依据和现象,并且把相关日志都给它们。过了两天,他们最后竟然只回了一句话“从给我的两个日志来看,没有找到任何与XXX 有关的东西....”。TMD !我真的很生气 ,它们的产品都折磨我们半年之久了。不过,看他们对IBM 的WAS 和JVM也不懂,我也就不想再说什么了。下面是我的邮件,公司机密部分都隐去了:
引用
附件是我们这段时间服务器宕机的日志。我们用IBM PatternModeling and Analysis Tool for Java Garbage Collector
Version 1.3.2
分析了一下虚拟机日志,没有发现是内存泄漏导致;用IBM HeapAnalyzer
Version 1.4.4 分析heap
文件,也没有发现很可疑的内存泄漏。
我想以前你们也这样做过,现在我们分析错误日志,发现有一个现象,在宕机时,总是找不到文件,我看就是Websphere
或是AIX
IO资源不够,不知道是什么导致的。但是,我们自己的应用,基本上没有什么IO
,除了一次load几个配置文件。不过,我觉得你们WCM
的IO操作挺多的,不知道你对日志有什么新的发现。
客观的说,这几个月来,宕机那台服务器,除了你们的XXX
,就以论坛和blog为主,而且他们都是开源的。在频繁宕机的06
年11月份,我们的论坛和blog
还没有上线。现在我们不得不每天晚上11点定时重启,但这也不是长久之计。
现在,我们进行分离遇到很大阻力,原来想把你们的XXX
单独分离出来,在当前的环境下,不是很现实,如安装、测试(负载、定时服务),所以现在分离我们自己的应用,但当前在产品环境下,客户方阻力也很大。
希望尽快能够得到你们的问题建议和方案。
文中说到了IBM 的两个分析工具,这也是我们后来的救星:我们就是需要这种离线分析 工具,因为实时检测 已经证明不现实。但我始终对该分析出来的结果抱怀疑态度,直到我去深入IBM的JVM 以及和IBM 的技术支持交流......
柳暗花明啊 ,至少看到了一点希望,不过最后我们还是失望而返。
四、用IBM 的HeapAnalyzer 和GarbageCollector 检测
找到这两个工具,已经是够费劲了,因为以前找的IBM
HeapRoot
工具,让我对这类工具很失望。而且,这两个工具,只有在IBM
的Techinical
Support网站能够搜索到,但很不容易,因为那两个工具,并不是象IBM
的Websphere产品那样宣传,它只在IBM Techinical
Support
文章的某些角落里出现。要知道,Techinical Support是IBM
很重要的收入来源,这类文档,他们并不会让你很轻易就拿到,比起BEA
WLS的支持网站dev2dev
差远了。
具体诊断细节我就不详述了。我认为,IBM
的WAS
或JVM出了性能和OOM
问题,这两个工具是最有效的,而且是离线分析工具,比起那些实时Profiler工具,某些场合有绝对的优势:譬如我们目前的产品环境,你只能分析宕机后的日志,实时分析前面已经验证是不可行的。
从日志分析,我们最终得出结论,我们购买的CMS
系统有严重的碎片(大对象)问题,而该问题是OOM的罪魁祸首,而且IBM
工程师也得出了同一结论。不过,在起先我们得出这一结论一周后,我还始终不相信heap碎片会导致OOM
,直到IBM
工程师总是向我强调。
我想很多人也是不太相信,因为大多数人用的都是Sun 的JVM ,譬如Windows 、Solaris上的hotspot 。而且,SunJVM 出问题,如果是配置的问题,一般通过配置heap 最大最小值,以及maxPermSize都可以解决。Heap 碎片导致的OOM,只有BEA 的JRockit 和IBM JVM上发生,不过JRockit 有专门文档说明,而且很容易找到(就在jdk的文档里面)。
配置heap 最小最大值 ,我想大多数人都有经验。对于Sun 的JVM 来说,一般可以设置heap 最大最小值一致,也是推荐的做法。因为它的GC策略默认是复制、分代算法。也就是说,它会将heap 分成不同的几个区,譬如Solaris JVM中最上面有两个大小相等的区。GC 时刻,将一个区的存活对象复制到另外一个对等区,垃圾对象就算遗弃了。这样在heap里面,就不存在碎片问题。另外,根据Java 对象的存活期不同,将之转移到不同的区(Tenured区),存活最长的在最底部(火车算法),这也就是分代模型。具体请参考官方文档:http://java.sun.com/docs/hotspot/gc1.4.2/
对于maxPermSize
(Permanent
Generation
),
主要和那些加载到JVM
里面的JavaClass
对象相关,它的空间不是在Java Heap
里面分配。如果你当前的heap有1000M
,permSize是200M
,那么JVM
至少占用1200M
。
在这个空间内的对象的生存期和JVM
是一样的,譬如JDK
的核心类库,它们被System Classloader加载到JVM
的Method Area(方法区)后,就不会被GC
掉的,这些对象一般是Class对象,而不是普通的实例对象,也就是JVM
的元数据。我们在用反射时经常用到它们。所以,对于现在象Spring、Hibernate
这些框架经常通过反射创建实例,可能对maxPermSize要求就大了,缺省的64M
很多时候是不够的,特别是对于应用服务器里的应用,象JSP
就会产生和加载很多classes。不过,如果是它导致的OOM
,一般会有类似 perm size提示。
但是,对于IBM 的JVM ,情况就完全不一样。它的默认GC策略并没有采取复制、分代。这个可以从GC 日志分析出来。它不像Sun的JVM 那样,有个单独的方法区,它的方法区就放在Java Heap里面。JVM 规范里面并没有要求方法区的必须存放的位置,因为它只是一个JVM实现问题。
在IBM
的JVM
里面,这些对象一般分配在称为k-cluster和p-cluster
里(cluster又是属于Heap
),而后者一般是临时在heap里面申请。并且,这些cluster
是不能GC,或是被移动重排的(Compact
过程)。这就导致Java
Heap里面就如同马蜂窝,但不同的蜂孔又不能合并,于是,当我们程序里面产生一个大对象,譬如2M
的数组(数组必须分配在连续的内存区)
时,就没有可分配空间了,于是就报告OOM。这些不能被移动的cluster
就称为所谓的碎片。此时,JVM的Heap
利用率可能不到50%
。
当然,通过一定时期的GC
日志,可以计算出cluster
的合理大小(专门在Java
Heap的底部),另外,还可以为这些大对象专门分配大对象区的(超过64k
的对象)。
通过上面的理论介绍,我想大家一定知道了为什么IBM
的JVM
里面不推荐heap
的最大最小值相同,因为这样碎片问题会非常严重:如果我们每次大对象申请内存时,heap都扩展5%
,譬如50M
,碎片问题很大程度上可以避开,程序性能也高些(寻找可用空隙和分配耗时,以及每次GC时间拉长)。
以上的具体阐述,请参考我在上文推荐的几个URL
,另外再推荐三个宝贵的链接:
http://www-1.ibm.com/support/docview.wss?rs=180&context=SSEQTP&q1=fragmentation&uid=swg21176363&loc=en_US&cs=utf-8&lang=en
http://www-900.ibm.com/cn/support/viewdoc/detail?DocId=2447476A10000
(IBM
技术支持告诉我的,太重要了!)
http://www-900.ibm.com/cn/support/viewdoc/detail?DocId=2847476B08000
我想大家应该会问:我怎么能够肯定我的OOM
问题是heap
碎片造成的呢?下面的方法可以验证。
在OOM
发生时,JVM
会产生一个heapdump文件。然后用GarbageCollector
分析出该OOM发生时刻,JVM
去申请的空间,譬如约235k。此时,你再用HeapAnalyzer
去分析此时的heap快照里面的gap
size
大小(空隙大小)和各自的可用数目。你会发现,大于235k的空隙个数为0
。这就是碎片导致OOM
的证据。
另外,有人会问:我怀疑我的OOM 是因为程序内存泄漏造成的,怎么去验证?
你可以用HeapAnalyzer
分析发生OOM
时刻的heap快照,工具会罗列出哪些对象怀疑有内存泄漏,譬如Cache
对象都非常大(但你可以确定它不是内存泄漏)。另外,分析这次宕机(从这次虚拟机启动到宕机这段时间)的heap走势,如果曲线明显是向上倾斜,也就是那种典型的内存泄漏图,就有可能是内存泄漏。当然,还必须结合heap快照。
内存持续上升在JVM
开始一段时间很正常,因为JVM
对第一次访问到的Class 对象,譬如一个典型的Web
应用,就有jdk的class
、Spring
或Hibernate的class
对象,它们都会被缓存下来(ClassLoader原理)
,一般均不会被GC。当大多数class
对象缓存差不多(当然还可能有一些Singleton对象,不过不怎么占分量),JVM
的Heap就平稳了,呈一水平波浪或锯齿线。
如果可以用JProfiler
这类工具实时监控,就更容易确诊了。
经过一番周折,我们终于看到了一线希望了 。
在一定的准备后,我们决定对WAS
进行性能调优了。WAS
的调优参数,可以分为两个部分:JVM级别和WAS
级别:
JVM
:主要是GC
和Heap
。
WAS:Thread Pool
,JDBC
DataSource
。
当然要调节,你需要明白你的目标是什么,调节依据是什么,怎么计算,绝对不是凭空想象的,譬如heap
最小值1024M
,日志证明,该参数非常不适合我们的环境。具体细节,留给后文吧。
战战兢兢地,中午12:00 ,我们给产品环境下的WAS 调节参数、重启,同时优化了AIX的IO 相关参数。
我试着设置了一下JVM
的k-cluster
和p-cluster
。下午15:00左右,WAS
挂了,AIX
也挂了。这下麻烦可大了。我们都慌了,马山客户的老总就来电话了,一阵哗哗啦啦。实在无奈,让客户那边工作人员通知机房(服务器托管处)工作人员重启AIX。我也不得不强行更改刚才的参数,立即设为另外一个值。
其实,我把那个两个cluster
值确实设置太大了,我把它们设置为推荐值的5倍,譬如p-cluster
是65k×110%×5
。另外一个愚蠢的设置就是把最小heap设置为2048M
(AIX有4G
内存)。
后来我恢复到约正常的值,也就是去掉那个cluster
的5
,另外分配了一个30%的大对象区(如果1000M
的heap
,就是700M
+300M
)。
就这样,系统持续正常运行了三天,以前可是一天一down
。当在三天后再次宕机时,我们都没有自信了
。不得不通过AIX
的cron
,继续每天深夜11点的WAS
定时重启。
不过,那次宕机,包括以后的几次宕机,再也没有出现OOM
错误了
,但系统依然不稳定。虽然我可以说OOM问题解决了,但领导和客户需要的并不是这种结果。
其实,在这个时候,我们已经发现我们系统的四大问题:
1
、WAS
和JVM
参数:OOM问题
2
、AIX
的IO
和Paging Spacing不足:AIX
日志后来显示错误
3、AIX
的WAS
分区空间不够:WAS的日志膨胀一周就把那个opt
分区塞满了。
4、应用程序的JDBC
连接池:我们20
来个应用,一个20
connections,DB2
数据库有时被撑死。
也就是说,我们最初在客户那儿部署时,用的默认值根本不行。而且,部署涉及多人,人员之间出现断层。如果我们只是按OOM
,无疑是走入死胡同,必须全局考虑!
但是,项目组实力薄弱,公司范围内就没有对AIX
精通的。不过项目组原来有一个搞银行系统,在AIX下开发,就他熟悉些。我当时对AIX
也比较陌生,你们从Linux转到AIX
,你就知道它有多别扭了。命令都自定一套(也许因为是Unix元老吧),那个shell
也超级别扭,而且参考书特少。不是自诩,我两年前负责一个高负载的Linux服务器管理一年多,也是玩得很转的。
就这样,他负责AIX
的相关问题,我负责WAS
相关的。
但是,现实环境,已经不允许我们再试验下去了
。我们必须找到一条绝对可靠的对策!
这就是下文的CMS
系统大迁移,服务器再次优化。
五、隔离CMS
系统,服务器优化
从前面的介绍,大家应该记得,我们开始是固定CMS
,分离其它应用,但遭遇失败。现在是反过来,干脆把CMS
系统赶出WAS
平台
。
说实话,项目经理做这个决定,我认为已经是鼓出很大勇气了 。
当时我们想在一个备用AIX
机上安装CMS
产品测试,但最后还是没有做成:
CMS
这类文章发布系统很难安装,也不好测试,又没有liscence
,而且还有一堆准备工作。绝对没有著名的openCMS安装那么简单,当然功能远远比它复杂。而且,我们当时也低估了后来的工作,总觉得问题好解决。
在很遥远的06 年中期,CMS 厂商在客户那边一台AIX 的Tomcat 上部署了一套CMS产品。但当时客户执意要求将其跑在WAS 上,也就是现在的情况。最开始,客户还要求我们必须用WAS的集群( 我们买的就是WAS 的ND版) ,无奈该CMS 不支持。要是集群,又是死伤一遍。其实,现在想想,我们当时太被动,CMS这种东西,就供公司的几十个编辑用,一个普通Tomcat 就完全够用。而且,把它和面向公网的Internet应用混在一起,完全没有必要。也许,被动是因为我的实力造成的。
我们决定背水一战时,已经做过周密的计划:某年某月某日晚上8:00......
CMS
产品负责人现场切换
xx
(我)负责WAS
相关参数调整
yy
负责AIX
参数。
zz
负责应用的测试
…..
总之,该行动涉及到客户方、产品提供商、公司高层、项目组。每个人都密切关注,不下20 人。每个人都守在电脑前,随时听候调遣,当天晚上,我们都没有准备回家睡觉,大家齐心协力。
真没想到,整个式切换工作,一个小时就顺利完成 !第二天,客户编辑打开浏览器,她们一定想不到昨晚大家准备经历一场厮杀….
系统持续平稳地运行了一周,然后是漫长的五一,我回湖北黄冈老家休息了八天。回来时,一切依旧。