64位Linux下Java进程堆外内存迷之64M问题
起因,监控系统检查到link机器内存耗尽,用top查看,果然有一个java进程占用了近24g内存(包括VIRT和RES),但是启动java进程的参数是java -server -Xmx12g -Xms12g -XX:PermSize=50m,理论上只应该占用12g再多一些,所以问题就在这个进程了。此进程主要处理Socket IO读写,使用的是Java NIO。
http://stackoverflow.com/questions/561245/virtual-memory-usage-from-java-under-linux-too-much-memory-used 这里详细描述了top命令里内存显示的意义。
用jstat -gcutil $pid看,发现堆使用率却很低,甚至都没有怎么出现过Full GC,Young GC也很少,堆内存完全够用。jmap -histo:live $pid命令(注:此命令调用会先调用Full GC一次)
也看不出有任何对象占用内存很大的情况。
到这里据同事提醒,怀疑是堆外内存泄露。一般情况下是ByteBuffer.allocateDirect()
这个API可以开辟堆外内存空间,但是搜索源代码并没有发现有这个调用。
然后据水寒同学提示,可以通过cat /proc/$pid/maps
(也可以通过pmap $pid
命令)来查看内存使用。然后就发现了问题:
- 一大块内存正好是12g,应该就是通过-Xmx设置的内存,无标识符
- 有一块3.5g的内存,最后一列标识符是[heap]
- 有111块正好为64M或者非常接近64M的内存块,无标识符
显然问题出在这111 * 64m = 6g这里,搜索源代码并没有发现有常量64的使用,一般代码也不会硬coding这么大一块内存呀。如果问题出在引用的某个第三方jar包就难定位问题了。
因为觉得这个64M内存块具有非常显著的特点,于是就通过Google和百度,找到如下:
http://blog.2baxb.me/archives/918
https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage
http://it.taocms.org/01/6808.htm
https://infobright.com/blog/malloc_arena_max/
这几篇文章,尤其是ibm那篇文章讲的最细致,Linux glibc >= 2.10 (RHEL 6) malloc may show excessive virtual memory usage。
大致意思是,这个版本的glibc控制的malloc函数,会在64位的Linux的中,给每一个CPU核开辟8个64M内存的缓冲区,用于提高性能。
然后这个问题也被Hadoop官方发现并指出,然后推荐控制环境变量MALLOC_ARENA_MAX=4来达到最优设置。
修改方法:启动java进程脚本前面加入,export MALLOC_ARENA_MAX=4