最近遇到這個Exception,發(fā)現(xiàn)不少開源框架在實現(xiàn)快速讀寫文件時,采用的都是FileChannel的map方法,示例如下:
at sun.nio.ch.FileChannelImpl.map0(Native Method) ~[?:1.8.0_40]
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:904) ~[?:1.8.0_40]
這個方法的作用是將文件映射到堆外內(nèi)存中,然后通過讀寫內(nèi)存來實現(xiàn)對文件的快速讀寫。從知乎摘了一張圖。

mmap display
問題 & 解決
目前來看,我在使用MapDB和Phoenix時都曾經(jīng)出現(xiàn)過這個Exception,MapDB直接將DB文件映射到堆外內(nèi)存,來達到高效的讀寫;Phoenix在結(jié)果集過大時,需要落地成臨時文件,而這個臨時文件是采用mmap來操作的。
Map Failed 出現(xiàn)的原因有很多:
1. 對于32-bit JVM,由于地址空間是32 bit,2^32=4GB,所以我們能映射的文件大小只有4GB,但實際上因為其他對象同樣需要占用地址空間,所以正常情況下,只能映射1GB左右。
2. Java程序有默認的maxDirectMemory,即JVM可使用的最大堆外內(nèi)存,稍不注意,就有可能超過并且拋出異常(如在Phoenix數(shù)據(jù)查詢中結(jié)果集過大)。
3. mmap句柄超過了系統(tǒng)默認最大值。系統(tǒng)默認最大值可使用```cat /proc/sys/vm/max_map_count```查看,進程使用句柄數(shù)可使用```cat /proc/$PID/maps | wc -l```查看。每一塊申請的ByteBuffer都對應(yīng)一個mmap句柄,也就是說,如果生成了大量的碎ByteBuffer,那么句柄數(shù)也會急劇增長。而這些句柄的回收是伴隨著ByteBuffer回收的,堆外內(nèi)存的GC需要顯示調(diào)用System.gc來進行。
解決方式:
1. 若為32-bit JVM,可升級到64-bit,獲得更大的地址空間。
2. 若為64-bit JVM,當超出堆外內(nèi)存不多且使用總內(nèi)存小于32GB時,使用-XX:useCompressedOops來壓縮對象的空間大小。
3. 調(diào)整-XX:maxDirectMemorySize來獲得更多的堆外內(nèi)存。
4. 調(diào)整/proc/sys/vm/max_map_count。