实战JAVA内存泄露问题(一)

2014-11-24 02:42:53 · 作者: · 浏览: 3

最近一个J2EE产品在生产环境下出现了内存泄露,下面是我们的主要处理步骤:

1. 下载gc的log,发现最近一年来,原来内存一直正常,呈锯齿型,就是内存多了会通过gc回收掉,又回到较低的水平;到9月份的时候开始,内存缓慢增长;这里值得注意的事该产品从去年到9月份没有做过代码、配置等任何更改。

到11月份开始,内存迅速增长,连续运行一个星期以上就要OutofMemory;

幸好该产品只需要支持24*6的服务,每周末重启一下暂时还不影响服务。

2. 在内存比较大的时候,生成一个heap dump,打开来看什么对象占内存最多,发现下面这个类的对象占了大量的内存

Java.awt.EventQueueItem

找到代码里面用到这个类的地方,发现有大概如下的代码

这是一段和第三方通信的代码,基本情况是第三方会不断的发很多事件过来让我们处理,当前的日志看是大约1s发送100个事件左右,每发送一个事件过来会会调用OnEventNotify函数,然后我们需要调用doProcess,这个是第三方提供的jar里面一个接口,第三方的代码会先在内部处理下,然后我们实现的一个OnProcess函数里面真正做事件处理;

// 过程好像比较纠结,第三方是个外国公司,我们公司的这段代码之前也是老外写的,我也还没搞明白为什么通知和处理要分开,直接想办法做成处理不就可以了么?!

onEventNofity(***)
{

java.awt.EventQueue.invokeLater(new Runnable(){
public void run() {
doProcess();
}
});

}这段代码肯定是有问题,这个invokelater里面代码看进去是用新生成的Runnable对象构造了一个java.awt.event.InvocationEvent对象,然后会放到系统有一个默认的java.awt.EventQueue实现对象里面,然后让一个java.awt. EventDispatchThread调度,然后一个一个执行这里的run方法;

首先相当于每次一个事件过来的时候就生成一个Runnable对象,进而生成InvocationEvent对象,占用了大量的内存,

可纠结的是为啥以前没问题?而且这个版本在我们开发服务器上跑了几个月现在还一直正常;

中间经过各个环境下的测试、日志分析、对比,终于慢慢发现,从对日志的时间分析看,现在的事件处理速度,就是doProcess这里太慢了,导致事件处理不过来,然后大量的Runnable和InvocationEvent对象堆积在java.awt.EventQueue里面,导致了内存的泄露;

9月份之前的app日志已经没有了,看不出来当时的事件是否有那么多,

而开发环境下,由于开发环境连的数据源也是第三方的开发环境,收到的数据相比正式生产环境要少一些,另外一个第三方处理相关的时间参数配置和生产服务器不一样,导致doProcess这里的处理时间在开发环境下更快一些,从而可以使处理速度赶上收到事件的速度,不会有Runnable和InvocationEvent对象的堆积,也不会有内存泄露

这里写了一个测试代码说明这个内存泄露的过程,刚刚我在自己机器上跑了一下的输出是

Thu Dec 09 07:19:38 CST 2010 before memory:2031616

Thu Dec 09 07:19:38 CST 2010 after memory:3432448

Thu Dec 09 07:20:22 CST 2010 now system exit

可以看到,在这个过程中,有140MB的内存堆积了,由于在实际生产环境下事件不停的发送过来,里面处理速度始终赶不上,导致内存慢慢堆积,最终OutOfMemory;

下面是具体代码:

package com.hetaoblog.demo;
import java.util.Date;


public class MemoryLeakTest {

public static int n = 1;
static int count = 20000;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub

System.out.println(new Date() + " before memory:" + Runtime.getRuntime().totalMemory());

for(int i = 0; i < count; ++i)
{
java.awt.EventQueue.invokeLater(new Runnable(){

public void run() {

try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

n++;

if(n == count)
{
System.out.println(new Date() + " now system exit");
}

}

});
}

System.out.println(new Date() +" after memory:" + Runtime.getRuntime().totalMemory());

}

}

-------------------------------------------------------------------------------
昨天讲了内存泄露问题的原因其实就是在下面这段代码中无缘无故的搞个