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这个参数更稳妥一点。

相关文章
相关标签/搜索