前段時間在測試過程當中發現了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和本地內存之間的拷貝操做,尤爲是數據量較大時應該更加明顯。