正确解决OutOfMemeoryError中heap space故障
作者:Java伴侣 日期:2008-09-28
这段时间,我们的J10平台总是报OutOfMemoryError,基本平均在7小时左右就会报错,停止服务。而那段时间我的工作重心被安排倾向在搜索查作弊上,平台一直是技术经理Kenny在维护了,也由他查这个问题,我有点轻视了这个问题,仅按照网络的一些文章提示检查和配置了一些Java虚拟机的参数,然后由K去检查和排除故障点,所以直到第三天我制订好了防作弊的业务规则后,才又注意到这个报错一直在发生。
然后找K来聊,他说网上的文章都说是加大内存配置一个-Xms -Xmn -Xmx之类的内容就可以,可是他暂时不清楚配置在哪里。我就开始跟踪报错时日志,从中找流程脉络,先花了十分钟找到一个文件I/O的资源泄露,再花了五分钟找到一个内存溢出的代码段,改掉这两个地方后,目前运作到第五天了,没有再报内存溢出的错误。以下把查找这类错误的经验和大家交流一下,希望大家以后碰到类似问题时,不要完全信奉拿来主义甚至以拿来为标准不加思索的去用,而要根据你的知识去根据jvm日志(我用resin时看jvm-default.log,有人回帖提示websphere和weblog是HeapDump和Javacore文件)分析,去读自己的代码,找根本原因才是解决之道。
这里我把查这个错的工作过程介绍一下,其中的示例代码并非真正工作当中的代码,而是点出重点:
一、根本思路
1、OutOfMemoryError所报的heap space错误是堆的空间不足,大家都知道,heap(堆)是动态分配大小的区域,不需要预声明空间大小,并且由JVM自动管理和回收。
2、heap里存放的是非共享的过程性的变量或者资源的,也就是说错误原因就是heap里的数据一直增长发生了错误。
综上所述,错误原因是过程当中对一些资源没有释放、内存没有释放导致的。特别需要注意的就是容器里的数据。
二、过程示例
我首先去看我的jvm的日志文件,如果你是自己的client型软件,那么养成良好习惯记得自己做一下logging操作,对于异常要进行exception.printStackTrace(OutputStream)的操作。我做的是b/s项目,使用的resin-3.1.5平台,resin-3.1.5的jvm日志存放在log/jvm-default.log文件。我用vi指令打开jvm-default.log文件,键入“:$”跳到最后一行,再键入“?OutOfMemoryError”,这样其实可以看到jvm打出来的错误报告,尽量找到这件故障第一次报错时的错误提示,以后的错误提示可信度不高,因为内存管理已经紊乱。例如我看到的错原因是:
- java.lang.OutOfMemoryError: Java <SPAN class=hilite1>heap</SPAN> <SPAN class=hilite2>space</SPAN>
- at com.caucho.server.http.HttpRequest.<init>(HttpRequest.java:102)
- at com.caucho.server.http.HttpProtocol.createRequest(HttpProtocol.java:70)
- at com.caucho.server.port.Port.run(Port.java:1428)
- at java.lang.Thread.run(Thread.java:619)
- at com.caucho.server.http.HttpRequest.<init>(HttpRequest.java:102)
- at com.caucho.server.http.HttpProtocol.createRequest(HttpProtocol.java:70)
- at com.caucho.server.port.Port.run(Port.java:1428)
- at java.lang.Thread.run(Thread.java:619)
我先说一下这个是不准确的,因为系统已经乱了。为什么这么说呢,因为这个报的是系统级错误了。呵,根据前面我说的heap space的故障原因来说,故障应该是由我们自己的故障引起的(因为resin是很健壮的,这个信任是前提)。
然后看到第一个报错原因是这样的:
- [12:31:47.372] {http--8080-10} java.lang.OutOfMemoryError: Java <SPAN class=hilite1>heap</SPAN> <SPAN class=hilite2>space</SPAN>
- [12:31:47.372] {http--8080-10} at java.util.Properties$LineReader.<init>(Properties.java:389)
- [12:31:47.372] {http--8080-10} at java.util.Properties.load(Properties.java:325)
- [12:31:47.372] {http--8080-10} at amun.util.PropertiesUtil.property(PropertiesUtil.java:40)
- [12:31:47.372] {http--8080-10} at j10.ads.J10Ad.getMidAndAgentAsString(J10Ad.java:207)
- [12:31:47.372] {http--8080-10} at _jsp._ads._3040._before__jsp._jspService(_before__jsp.java:198)
立马打开PropertiesUtil.property()方法,仔细一看果然是代码出现了资源泄露的问题。修改它后重启,仅花了十分钟左右。
第二次时又发生错误,我如法炮法,找到另一个内存溢出的问题。
三、什么是资源泄露?
这个资源一般界定为一种可用I/O句柄、物理连接句柄之类的“必须显示声明释放的句柄”我称他为资源。如上面所述的一个资源泄露的例子就是代码中忘了关掉这类句柄导致的资源泄露,来,实战一下:
- // ........
- private static Property prop = null;
- private static String file = null;
- public staitc void config(String path){
- this.file = path;
- }
- public static String property(String key){
- if ( file == null )
- return null;
- //........
- FileInputStream input = new FileInputStream(file);
- //这里每次重新创建一次,目的本来是为了达到动态生效的。
- prop = new Properties();
- prop.load(input);
- return prop.getProperty(key);
- }
嚯嚯,这里的input就是一个已打开的文件I/O句柄,这类具有物理连接性质的“资源”,Java是不会回收的,如果没有做input.close()操作,jvm会认为这个资源是一直被使用的,所以,当我们的访问量很大到累积到一定程度时,就出错了。我当时改写成这样的了:
- public static String property(String key) {
- if ( file == null )
- return null;
- try {
- if ( prop == null ){
- FileInputStream input = new FileInputStream(file);
- prop = new Properties();
- prop.load(fis);
- <STRONG>input.close();</STRONG>
- }
- } catch (Exception e) {
- logger.error(e.getMessage());
- e.printStackTrace();
- }
- return prop.getProperty(key);
- }
进行了input.close()的操作,这样,jvm就会回收了。
四、什么是内存泄露
直接说例子了,象这次出现的一个内存泄露的情况是一个这样的代码。jsp会调用一个bean AreaIP里的方法getXXIP(),这个类大致是这样的:
- private static List<String> guangZhou = new ArrayList<String>();
- public static List<String> getGuangZhouIP(){
- String [] gzArray= { "one ip","two ip" }; //省略,原例子有80多个,他的原目的是便于手动增删;
- for(String gz : gzArray )
- guangZhou.add(gz);
- return guangZhou;
- }
其实这个guangZhou本身是放在stack(栈)里的,但是前段大量的访问调用后,这个错误的代码就在jsp里被当作heap space报出来了。jsp里的代码是这个样子的:
- List<String> example = new ArrayList<String>();
- example.addAll(AreaIP.getGuangZhouIP());
为什么会报heap错误,让各位朋友自己去思考一下,当作互动吧。呵呵
修改以后的代码是这样的:
- if ( guangZhou.size()<1 ){
- String [] gz = {};//此处省略
- for ( String g : gz )
- guangZhou.add(g);
- }
五、结论
其实从以上例子各位可能看出来了,点出这个错误后,大家都会哦的一声,估计没有人不懂的,你看我发出帖子,就有人回帖说这个是新手级错误了。不知道你们一个团队带了几个人?在一个团队中,难道你真的没有碰到过令你哭笑不得的新手级错误?恐怕有经验的人都经历过。
所以解决OutOfMemoryError中heap space错误,不是象网上搜出的几篇文章说的改一下jvm参数就行的,一定是你的程序当中有环节存在故障,象别人说的“新手级错误”,希望工作当中能避免!