64位Linux下Java进程堆外内存迷之64M问题

9月 9th, 2016 5,669 留下评论 阅读评论

起因,监控系统检查到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
Categories: Java 标签:, ,
  1. 还没有评论呢。