当然,作者本来的用意是好的,其实就是把收到事件和处理事件做异步化处理,本来的目的也是提高性能里的情况是按照第三方的API,OnEventNotify()需要尽快返回,否则会堵住后面发过来的事件;
onEventNofity(***)
{
java.awt.EventQueue.invokeLater(new Runnable(){
public void run() {
doProcess();
}
});
}这段代码的主要两个问题:
1. java.awt.invokeLater里面每次会新生成一个java.awt.event.InvocationEvent对象;
2.这里的Runnable的匿名类,其实本来完全可以单独定义的,现在这个写法,相当于每次一个事件过来都new出一个Runnable的匿名类来。。。白白浪费了大量内存
导致内存泄露的情况就是事件现在较多,doProcess占用时间太长,导致java.awt.event.InvocationEvent对象大量堆积;
所以,解决思路其实比较明确,
1. 要么提高处理速度使得事件不堆积,
2. 要么改成其他异步化的方式,避免每次生成java.awt.event.InvocationEvent对象
在开发环境下连接到对方QA环境,使得数据量保持一致,重现了内存泄露问题后,经过分析调试测试,下面几种方案都在开发环境下运行良好,内存保持稳定,gc日志和heapdump都很正常,解决了内存泄露问题:
1. 修改了两个第三方API相关的配置参数,使得doProcess这里处理速度提高了很多,于是即使在现在的事件数量下,仍然可以避免事件堆积;
2. 由于事件处理是该模块的核心,而且凡是程序运行的时候必然会有事件不停的发送过来,所以,修改代码如下
onEventNofity(***)
{
//这里改成啥事也不做
}
然后程序起来的时候开一个线程不停的去做doProcess的事情;
由于这里onEventNotify仅仅是起个通知的作用,真正的事件是放在内部的另外一个队列里,所以这样做是可以的;如果有参数依赖,那可以在这个onEventNotify调用其他线程池的方式去做
----------------------------------------------------------------------------------------
之前写了两篇实战java内存泄露的话题文章(诊断和代码分析,解决方案),转载到水木时,引起了一些讨论,这里对一些问题做一些说明;
1. 下面的观点是我最赞同的,对岸那老外似乎GUI写的太多了,没事在这里直接写了这行代码,java.awt.EventQueue.invokeLater(new Runnable():
Kobe2000:
非GUI程序怎么能用invokexxx来异步执行呢,这个本来就有问题
icespace (蝈蝈):
服务器端的程序为毛要把doprocess放到awt的线程里头去
这里头没GUI啥事阿
这么做结果可能是load一堆awt的类和迫使 系统调用一堆没用的GUI事件在线程里
这个应该一堆或者一个worker thread从queue里取啊
用不着new 一堆runnable出来不吧
核桃博客说明:
下面是JDK中java.awt.EventQueue.invokeLater的源代码
public static void invokeLater(Runnable runnable) {
Toolkit.getEventQueue().postEvent(
new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
}这里可以看到,每调用一次,除了外面自己的new Runnable对象以外,里面还会new 一个InvocationEvent对象,所以一来这里浪费了大量内存和CPU时间,不停的new出东西来;
而来的确正确的做法应该是搞个后台线程或者线程池来处理这些事件;
2. 这种情况究竟算不算内存泄露
这个问题其实很难严格界定,基本我理解是内存会保持一直增长的情况,那就是内存泄露了;
在这里由于内存问题是和处理速度相关的,可以理解成是性能问题引发内存问题,假设后面事件急剧减少保持一定时间的话有可能会处理完,内存会恢复。
但是就现实情况就是事件保持稳定发送,内存被放到queue里面后就慢慢增长了;当然这和传统书上说的放进去以后由于逻辑错误再也释放不掉的情况还是有所区别。
3. 有同学提出将事件持久化到数据库然后再慢慢处理的方式
这个在我看来是完全不可行的,主要原因:
a. 通常这样大量事件不停发送过来的系统需要尽快处理好这些事件,否则业务上就有问题了;要不然的话没必要不停发送过来;这个系统就属于这样的情况;
先保存到数据库然后出来慢慢处理时间上就延时好多了;
b. 无缘无故增加了数据库的大量负载;在第一篇里面说了,目前的数据量大概是1s有100个事件,就这样不停的保持每秒做100个insert到数据库,然后再从数据库读出这些事件来处理,对数据库的负载增加太大;
c. 另外,虽然没实际验证过,不过我谨慎怀疑保存这个,就是按照1s100个事件,就是平均10ms一个事件。就我项目实际经验,10ms做一个insert,差不多一次磁盘IO操作的时间,实际在Oracle 10g下跑差不多也就这个速度;碰上数据库压力大的时候10ms还搞不定;而且事件也不是完全均匀的,有高峰和低峰,碰上高峰的时候的话,保存到数据库这个地方,又会成为瓶颈。
4.在解决方案里面写了两个做法
a. 要么提高处理速度使得事件不堆积,
b. 要么改成其他异步化的方式,避免每次生成java.awt.event.InvocationEvent对象
下面是piggestbaby的回复:
1. 要么提高处理速度使得事件不堆积,
2. 要么改成其他异步化的方式,避免每次生成java.awt.event.InvocationEvent对象
1. 是正确的方向
2. 你确定不是由于 1 的改进而掩盖的吧
如果在 仅对 1 进行了改进,还是出现 2 的情况
那么原因可能有两个
a. awt event dispather 处理效率很低,比直接串行处理数据效率低很多
b. InvocationEvent 对象比你们系统内部的队列表示占用内存过大, new效率低
这里1和2是分开两种策略测试的:)所以不会存在1和2互相掩盖的情况;
其实只要处理能处理过来,经测试,仍然用java.awt.EventQueue.invokelater这样错误的方式也还是不会堆积起来,不会有内存泄露
-----------------------------------------------------------------
在实战java内存泄露问 题2-解决方案 里面说了,主要的一个解决思路是提高事件处理的速度,有一个情况是同一个版本的代码在我们的DEV和QA/UAT/PROD的处理速度始终不一致,即使把所有相关的配置(主要是数据源)都配成一样的,还是有