#关于mina1.7及其之前版本 Exception in thread "OutDbThread" java.lang.OutOfMemoryError: Java heap space可能的“臭虫”
同步blog http://redsnow-fenglin.iteye.com/blog/454328
引起OutOfMemoryError的因素可能很多,但有一点可以肯定的是和内存申请、释放有关,鉴于此,我来谈一下在做过的一个项目中,使用mina1.7遇到此问题的解决方案,下面,简单阐述一下项目出现此Exception的背景情形,以便有类似情形的朋友做个参考,找出这个相似的可能“臭虫”。
项目中mina的主要功能是做文件传输的,也是项目的核心,下面我将分阶段来阐述“臭虫”:
第一阶段:在项目模块测试时,系统启动内存就占去500多M,随着文件传输时间的推移,内存渐增,在数小时内增至1.2G多,之后就core掉了,问题很easy的定位在取货配送模块,即文件收发模块
第二阶段:core掉的异常提示为OurOfMemoryError,或java heap space不足引起jvm达到hotspot,直观原因是内存申请的问题,但内存申请是mina做的啊,于是,同事经过多番尝试,终于前进了很大一步,问题暂时解决了,那么,同事做了什么事呢?两点:一点是改mina 的 seesion.write为线程处理,即在发送的时候,开辟新的短线程来完成此工作,表现效果大大提高;而是调整jvm参数,增多jvm可申请内存及jvm回收机制;
第三阶段:该阶段是最要命的,因为已经进入了项目联调准备入网检测的阶段了,只有一周很紧的时间,core掉又跳出来了,表现形式和阶段一大致雷同,入网检测在即啊,大家忙得团团转,开会、讨论、请公司测试部门帮忙...,一时间项目被重视的高度白炽化了;下面,我先具体阐述下引起此bug的业务流程环节,以便有同样类似问题的朋友参考,然后在讲解一下我们的解决方法;
引起bug的业务流程环节:内存的增长,只与文件发送次数成正比,而与发送文件大小无关;
bug的解决过程:会议讨论并无结论,有的仅是各自的想法,上面的意思很简单,尽快解决问题,不影响项目入网检测,要求测试组全力协助我们开发组,测试组自然是从常规的测试入手,观察内存变化,定位引起变化的原因...,而我呢?想到内存增长与发送次数成正比,而无关文件大小,问题应该出在每次发送内存申请与释放上,会后10分钟 头 就问我是否有进展,我汗,看来头期待我能有所进展(也难怪,我是组内搞java最久的,另外几位都是为了项目而转为java),我告诉头:“还没,我在研究mina关于session.write的代码,那两个测试人员应该在做测试方面的监控”,...,先告诉大家结论:问题的确出在mina上,而其解决方法也很简单--该mina内存申请由direct方式为nod-direct方式,缩短mina池资源释放时间,具体解决过程为(问题的解决在于思路、过程,结果是附属的重要产物):
1、优化JVM参数,治标不治本,若真的存在bug,但我们期待简单的优化可以过关,然,无果,但还是建议有兴趣的朋友看看若下几个链接,了解下JVM参数优化的问题,相信你看过之后会觉得牺牲刚才时间是值得的:
JVM参数基础知识:http://blog.163.com/dxf_dgq_sureme/blog/static/97987377200932311451423/
http://jero.iteye.com/blog/75879
JVM参数白皮书:http://java.sun.com/performance/reference/whitepapers/tuning.html#section4.2
JVM参数优化实例,很不错的:http://topic.csdn.net/u/20081015/15/788c61e8-2773-41b6-988e-2b180f315678.html
2、既然上面是治标非本的方法,那我们不得不另寻它方;debug模式观察到mina首先启动线程一个池类PooledByteBufferAllocator,这便成了成功解决此问题的突破口,我们看一下这个类:
public class PooledByteBufferAllocator implements ByteBufferAllocator {
private static final int MINIMUM_CAPACITY = 1;
private static int threadId = 0;
private final Expirer expirer;
private final ExpiringStack[] heapBufferStacks = new ExpiringStack[] {
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), };
private final ExpiringStack[] directBufferStacks = new ExpiringStack[] {
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), new ExpiringStack(),
new ExpiringStack(), new ExpiringStack(), };
private int timeout;
private boolean disposed;
/**
* Creates a new instance with the default timeout.
*/
public PooledByteBufferAllocator() {
this(60);
}
/**
* Creates a new instance with the specified <tt>timeout</tt>.
*/
public PooledByteBufferAllocator(int timeout) {
setTimeout(timeout);
expirer = new Expirer();
expirer.start();
}
...
/**
* Sets the timeout value of this allocator in seconds.
*
* @param timeout <tt>0</tt> or negative value to disable timeout.
*/
public void setTimeout(int timeout) {
if (timeout < 0) {
timeout = 0;
}
this.timeout = timeout;
if (timeout > 0) {
}
}
public ByteBuffer allocate(int capacity, boolean direct) {
ensureNotDisposed();
UnexpandableByteBuffer ubb = allocate0(capacity, direct);
PooledByteBuffer buf = allocateContainer();
buf.init(ubb, true);
return buf;
}
private PooledByteBuffer allocateContainer() {
return new PooledByteBuffer();
}
...
}
是可以修改默认的60(秒),调小其之,加快程序资源释放周期,但第一点在哪里呢?在类ByteBuffer.java,因为我们程序中是用ByteBuffer来分配资源的,下面是其原码:
public abstract class ByteBuffer implements Comparable<ByteBuffer> {
private static ByteBufferAllocator allocator = new PooledByteBufferAllocator();
/**
* default is set true by mina
*/
private static boolean useDirectBuffers = true;
/**
* Returns the current allocator which manages the allocated buffers.
*/
public static ByteBufferAllocator getAllocator() {
return allocator;
}
/**
* Changes the current allocator with the specified one to manage
* the allocated buffers from now.
*/
public static void setAllocator(ByteBufferAllocator newAllocator) {
if (newAllocator == null) {
throw new NullPointerException("allocator");
}
ByteBufferAllocator oldAllocator = allocator;
allocator = newAllocator;
if (null != oldAllocator) {
oldAllocator.dispose();
}
}
public static boolean isUseDirectBuffers() {
return useDirectBuffers;
}
public static void setUseDirectBuffers(boolean useDirectBuffers) {
ByteBuffer.useDirectBuffers = useDirectBuffers;
}
...
}
从代码中我们可以看到,有用的信息是private static boolean useDirectBuffers = true;即使用直存方式申请内存,可以通过setUseDirectBuffers(boolean useDirectBuffers)方法修改其默认值
第四阶段:问题成功解决的方法:1、修改ByteBuffer.java内存申请方式默认为对战模式;2、缩短PooledByteBufferAllocator.java对Expire处理的时长;(一种方式直接改变源代码,一种方式修改程序、调整相应参数,我采用前者,因为文件传输模块非我开放维护,不便修改代码),顺便提下:mina其实也已经意识到此问题了,在mina2.0中已经修改了内存申请的默认方式为堆栈而非直存了,而且重写了池的管理,mina2.0已不具备后兼容性了,因为变化太大了;
经过上面的调整之后,系统启动占去内存已不足200M,在1小时内增长过两次,每次增长47M左右,之后稳定在200多M,运行24小时,表现良好,至此,问题告一段落,项目顺利通过入网检测各项指标考核,包括压力、疲劳测试;
第五阶段:大家一定疑惑为何会有如上的解决方案,其实第四阶段是在第五阶段之后产生的,本阶段的核心是:java内存使用方式及申请方式的关系,简单说就是直存方式速度快,但资源回收在GC之外,堆栈方式速度稍逊,但资源回收在GC之内,顺便提下GC并发优于并行及其它,详细的就不自我解说了,有兴趣的朋友可以看看如下信息:
JVM 如何使用 Windows 和 Linux 上的本机内存:http://www.ibm.com/developerworks/cn/java/j-nativememory-linux/
Direct vs. non-direct buffers http://java.sun.com/j2se/1.5.0/docs/api/ ByteBuffer
apche mina关于OutOfMemoryError 的mail list :http://mail-archives.apache.org/mod_mbox/mina-dev/200611.mbox/<8C29AD40787A694A81108B3FBF4C15F1CEAF6D@hue.cybersoft-vn.com>
http://mail-archives.apache.org/mod_mbox/mina-dev/200710.mbox/%3CABFD05FF09D13E4883B757E75603624DA3404A@ex-sea1.vistocorp.com%3E
似乎写的过于累赘,可能不太适合朋友们的口味,只是想阐述下具体的情况与之对应,因为Bug甚多,模式仅可借鉴非硬搬...