1. 程式人生 > >java.lang.OutOfMemoryError:Map failed總結

java.lang.OutOfMemoryError:Map failed總結

creat swap 超過 發現 byte 暴露 資料 unable 這也

常見的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相關的知識點。

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

跟蹤堆棧找到拋出異常的地方是在 FileChannle#map,這個方法是創建一個內存映射文件,應用為了降低堆內存的使用,同時提高寫入的效率,將一個文件分成多段,內存映射多個MappedByteBuffer進行讀寫操作;

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

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

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

當天這個機器的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。

確定了異常的觸發原因,再排查引發的原因就比較容易了,再看下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這個參數更穩妥一點。

java.lang.OutOfMemoryError:Map failed總結