前段时间在测试过程当中发现了mina框架的问题:当mina一次传输的文件超过必定值(如55m)或者连续传输文件的次数过于频繁,就会内存溢出:java
org.apache.mina.filter.codec.ProtocolEncoderException:java.lang.OutOfMemoryError: Java heap spacelinux
atorg.apache.mina.filter.codec.ProtocolCodecFilter.filterWrite(ProtocolCodecFilter.java:217)apache
atorg.apache.mina.common.support.AbstractIoFilterChain.callPreviousFilterWrite(AbstractIoFilterChain.java:361)windows
atorg.apache.mina.common.support.AbstractIoFilterChain.access$1300(AbstractIoFilterChain.java:53)服务器
atorg.apache.mina.common.support.AbstractIoFilterChain$EntryImpl$1.filterWrite(AbstractIoFilterChain.java:659)session
atorg.apache.mina.common.support.AbstractIoFilterChain$TailFilter.filterWrite(AbstractIoFilterChain.java:587)框架
atorg.apache.mina.common.support.AbstractIoFilterChain.callPreviousFilterWrite(AbstractIoFilterChain.java:361)jvm
atorg.apache.mina.common.support.AbstractIoFilterChain.fireFilterWrite(AbstractIoFilterChain.java:355)socket
atorg.apache.mina.transport.socket.nio.SocketSessionImpl.write0(SocketSessionImpl.java:166)函数
atorg.apache.mina.common.support.BaseIoSession.write(BaseIoSession.java:177)
atorg.apache.mina.common.support.BaseIoSession.write(BaseIoSession.java:168)
atcom.taobao.forest.server.DefaultPushTimeTask.pushcachetothesession(DefaultPushTimeTask.java:441)
1)开始是尝试用常规方法试图分析mina在内存溢出时什么东东占了那么多内存还没法释放,因而在jboss启动参数那加了两个参数-XX:HeapDumpPath=\tmp -XX:+HeapDumpOnOutOfMemoryError,做用是在发生OutOfMemoryError时将当时的内存映像dump到/tmp下,而后将dump出来的内存映像文件下到本地用mat分析,不过度析结果未发现有内存溢出问题,甚是奇怪。
2)以后,又上网查了些资料,才发现mina不是用的堆内存(Heap),而是使用的本机直接内存(Direct Memory)
所谓本地直接内存并非虚拟机运行时数据区的一部分,它根本就是本机内存而不是VM直接管理的区域。
在JDK1.4中新加入了NIO类,引入一种基于渠道与缓冲区的I/O方式,它能够经过本机Native函数库直接分配本机内存,而后经过一个存储在Java堆里面的DirectByteBuffer对象做为这块内存的引用进行操做。这样能在一些场景中显著提升性能,由于避免了在Java对和本机堆中来回复制数据。显然本机直接内存的分配不会受到Java堆大小的限制,可是即然是内存那确定仍是要受到本机物理内存(包括SWAP区或者Windows虚拟内存)的限制的,通常服务器管理员配置JVM参数时,会根据实际内存设置-Xmx等参数信息,但常常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操做系统级的限制),而致使动态扩展时出现OutOfMemoryError异常。
此外,按照jvm规范,本地直接内存的最大值按如下顺序设定:
(1)经过-XX:MaxDirectMemorySize=<size>指定值
(2)若(1)未知足,则就取maxMemory,也就是经过-Xmx设定的值;
(3)若(1)、(2)都未知足,则取默认值:64M;
根据以上知识,结合这次测试状况,问题基本水落石出:
在咱们测试平常机中,系统启动的时候设定-Xmx 3072m,没有经过-XX:MaxDirectMemorySize设定本地直接内存最大值,所以本地直接内存最大值就是-Xmx设定的值3072m,整个系统的物理内存为4G,除掉系统进程占用的内存,剩下的物理内存加上swap空间也就接近3G。设想JVM的heap size占用了1.5G,direct memory使用了1.5G,这时候程序申请一100M的direct内存,在这种状况下不管是JVM heap size仍是direct memory不知足触发gc的条件,因而jvm向os申请分配内存,可是OS却无可分配的内存了,因而就会抛出OutOfMemoryError错误。
所以,在使用NIO框架时的时候必定要注意:
若是该NIO框架使用的直存,需谨慎设定JVM运行参数,最好用-XX:MaxDirectMemorySize进行设定,不然你就得清楚你设定的-Xmx不仅仅制定了heap size的最大值,它同时也是direct memory的最大值;
再大概补充一下NIO和OOM知识:
1、首先对于可用内存这一律念的理解
在32位机器上,CPU可寻址的物理内存空间最大是4G,超出4G将再也不可见。【此处忽略PAE支持,若是进程中使用了AWE(windows)或者mmap(linux)一类的方案,这里暂时无论了】
这4G的物理内存空间又分为用户空间和内核空间。默认状况下,windows按照50:50的比例划分,linux 默认下用户空间3G,内核空间1G。
因此一个进程可用的物理内存空间,在linux32位机器下,就是3G 。而在64位机器下,基本上能够认为是没有任何限制,原理很简单了。。。
不论是linux仍是windows,可用内存空间由:物理内存+swap/虚拟内存组成。Linux上称做swap【交换空间】,windows上称做虚拟内存,本质上都是拿磁盘的一块地方看成物理内存使用。程序是不用关心使用的是物理内存,仍是swap;程序操做的是虚拟地址空间, OS再将虚拟地址空间映射到物理内存、文件或者其余。不论是操做物理内存,仍是swap,对于程序来讲彻底是透明的。
Swap/虚拟内存啥时候会使用,这个我也没彻底搞清楚,不过有一点应该没错的,就是进程新申请的内存,不会在swap/虚拟内存中分配,而是直接在物理内存中分配。当内存紧张时,OS会将活动进程中占用的内存,从物理内存中交换出来,放到Swap/虚拟内存上(有时甚至内存不紧张也会这么干)。当进程恢复活动时,OS再将数据从swap/虚拟内存空间中读出来放到物理内存中
因此当须要分析和计算进程须要占用的内存空间时,能够简单地忽略swap/虚拟内存的概念【这一点须要深刻再论证一下!】
2、JVM对内存的管理
画一张图,很容易就能够理解了,下面这个圆表示jvm进程所占用的全部的内存空间,分红三部分:
1. 堆空间
包括年轻化、年老代、持久域【以SUN HOTSPOT 虚拟机实现为例,其余虚拟机会有区别,好比IBM的虚拟机,所谓的“持久域”不是在堆分配,而是在本地内存】
若是这个空间不够了,会抛出java.lang.OutOfMemoryError
2. 栈空间
每一个线程都会有一个单独的stack空间,JDK5.0之前默认好象是256K,JDK5.0默认是1M,很大的一个数值,能够经过-Xss设置。若是这个空间不够了,会抛出java.lang.StackOverflowError
3. 本地内存
Jvm进程可以使用的内存,除去堆、栈空间以后,剩下来的就是本地内存
以上三个空间加起来的内存,就是最终jvm进程所使用的全部内存。若是是在32位机器下,不能超过用户空间大小,即3G;在64位机器下,就要看物理内存的大小了
另再提醒一下你们,在发生了内存不足时,一味地增长-Xms和-Xmx,颇有可能会拔苗助长,道理应该很明显了。须要看OOM的类型,是堆不足,仍是栈(StackOverFlow)不足,仍是本地内存不足native memory 。jvm通常都会有足够的信息提示的。
3、Nio的direct memory allocate
我理解的,NIO的直接内存分配【DMA】,应该是从本地内存区域中分配内存。像前面讲的,若是不使用-XX:MaxDirectMemorySize设置,那它就会使用-Xms的设置,以平常测试环境为例, 这种状况下DMA须要3G,堆也须要3G,很明显实际上这两个空间获得的内存都不可能这么大,因此要么是堆空间被挤压,拿不到3G,要么是DMA拿不到足够的空间
看jvm抛出来的错误,应该是堆空间被挤压致使的。若是是本地内存不足,抛出的应该是OutOfMemoryError :Direct buffermemory,能够看一下java.nio. DirectByteBuffer这个类的源码,98行
4、NIO2.0的改进
NIO的DMA,性能确定比在堆中分配要好得多,由于是直接操做本地内存,避免了数据在JVMHeap和本地内存之间的拷贝操做,尤为是数据量较大时应该更加明显。