java.lang.OutOfMemoryError:Map failed總結

常見的OOM是如下這幾種:
1.GC overhead limit exceeded
2.Java Heap Space
3.Unable to create new native thread
4.PermGen Space
5.Direct buffer memory
6.request {} bytes for {}. Out of swap space?
一直自認爲不會有超過這個範圍的OOM類型出現,沒想到最近看到了一個新的OOM的類型,而此次OOM引起了一次嚴重的故障,整個排查過程是內部一個同事排查的,文章也是他寫的,感謝他的文章,讓我也學習到了以前遺漏的一個OOM相關的知識點。java

故障現象爲
應用日誌中發現了大量的OOM異常:
Caused by: java.lang.OutOfMemoryError: Map failedapp

跟蹤堆棧找到拋出異常的地方是在 FileChannle#map,這個方法是建立一個內存映射文件,應用爲了下降堆內存的使用,同時提升寫入的效率,將一個文件分紅多段,內存映射多個MappedByteBuffer進行讀寫操做;函數

跟蹤fileChannle.map的方法發現最終調用的是FileChannelImpl.c裏的方法;學習

繼續跟蹤這段代碼,發現裏面調用的mmap64這個系統函數,當mmap64返回的錯誤碼是ENOMEM時,會向上拋出OOME,進一步查閱了GNU的手冊,能夠發現拋出ENOMEM錯誤碼的解釋:
1. 內存不足;
2. 地址空間不足。測試

而從當時的現場信息來看,這兩點都不成立,當時沒有新的思路,因而就先按照FileChannleImpl.unmap方法中的主動釋放佔用內存的方法改了下代碼,改了後應用就一切正常了。spa

當天這個機器的JVM還crash了一次,crash日誌中heap佔用和物理內存都是很是正常,但日誌中有個現象比較詭異: Dynamic libraries:這部分信息很是多,統計之後發現有65532條。日誌

翻閱資料,發現這個數據來自 /proc/{pid}/maps, 這個文件展現了進程的虛擬地址空間的使用狀況,這時忽然想到ENOMEM中有說到進程的地址空間不足致使的,可是最後的7fff005aa000還遠不到上限,並且計算虛擬內存佔用也就幾個G的空間。
這時想到前面提到65532這個數據,聯想到了file-max,可是一查看是4889494,順勢猜測虛擬內存映射是否是也有打開上限? 不出所料果真是有限制的。進程

max_map_count這個參數就是容許一個進程在VMAs(虛擬內存區域)擁有最大數量,VMA是一個連續的虛擬地址空間,當進程建立一個內存映像文件時VMA的地址空間就會增長,當達到max_map_count了就是返回out of memory errors。
這個數據經過下面的命令能夠查看:
cat /proc/sys/vm/max_map_count內存

發現應用所在的機器這個數值果真是65536,並且測試修改max_map_count後filechannel#map的個數的上限也隨之變化。 因此能夠肯定程序OOM是因爲達到了這個系統的上限,也就是ENOMEM錯誤碼中所指的out of process address。ci

肯定了異常的觸發緣由,再排查引起的緣由就比較容易了,再看下FileChannleImp#map的代碼,發如今map第一次出現OOM時,會顯式的調用System.gc去回收,但不幸的是應用啓動參數上是有-XX:+DisableExplicitGC的,因此就致使了map失敗,但若是在代碼裏主動clean是ok的現象。

總結來講,這個異常出現的緣由是:
數據量增加,致使map file個數增加,應用啓動參數上有-XX:+DisableExplicitGC,致使了在map file個數到達了max_map_count後直接OOM了(這也是由於heap比較大,因此full gc觸發的頻率低,這個問題就特別容易暴露)。

從這個問題來看,啓動參數上加-XX:+DisableExplicitGC確實仍是要當心,不只map file這裏是在OOM後靠顯式的去執行System.gc來回收,Direct ByteBuffer其實也是這樣,而這兩個場景都有可能由於java自己full gc執行不頻繁,致使達到了限制條件(例如map file個數達到max_map_count,而Direct ByteBuffer達到MaxDirectMemorySize),因此在CMS GC的場景下,看來仍是去掉這個參數,改成加上-XX:+ExplicitGCInvokesConcurrent這個參數更穩妥一點。

相關文章
相關標籤/搜索