赞
踩
之前打的服务镜像被告知在集群上运行时占用了很多内存。
但我其实是很纳闷的,因为之前也在服务器上也做过测试,对内存的消耗很正常,大概只有500M上下,如今却被告知容器单启动后就使用了2G内存。
先去证实一下,拿到了一台曾经被用作集群节点的服务器的使用权,把镜像传过去跑一下。
docker load -i XXX.tar
docker run -it -m xxxM --cpus=x -e XXXX=xxxx -v XXXX=xxxx -p xxx:xxx XXX:x.x.x
简单看一下服务进程的资源使用情况
docker stats <containerId>
或者
docker container top <containerId>
top -p <pid>
此处没有图的,工作机截图不方便外传。发现RES [ resident memory usage ] 确实是2个G,很奇怪。
通过-v的挂载路径把jar包取出来跑,其实在容器中运行的进程也是运行在服务器OS上的,但把jar包取出来部署方便使用其他工具监视。
java -XX:NativeMemoryTracking=detail -jar xxx.jar
先看一下资源占用
top -p <pid>
好吧,还是2G。
看一下是什么东西占了这么多内存
jmap -histo <pid>
发现堆里最多的是char[]类型,大概有800M,纳闷了,看看是什么东西。
做个堆快照
jmap -dump:format=b,file=xxx.hprof <pid>
分析一下,首先发现堆使用大小只有900M,奇怪了,那2G内存是哪用的?再看一下char[]都是些什么,实例数太多了,但随机抽着看发现都是些类加载的信息,应该可以被GC掉(确实可以被GC掉)。
先看看详细的内存分配。可以用jcmd统计java heap申请的地址空间的和pmap的地址空间对比一下,可以看到操作系统统计的java heap实际使用的物理内存大小。
jcmd <pid> VM.native_memory detail
pmap -x <pid>
按地址空间发现堆内存在pmap中被统计使用了1600M。
和之前的堆快照对比一下,发现RES中统计堆内存使用的物理内存要明显高于实际使用的堆大小。
做下GC。
jcmd <pid> GC.run
发现那些char[]只剩20M了,使用的堆内存也只有100M,但进程RES依然是2G,pmap中统计的堆内存使用的物理内存依然是1600M。
也就是说,在GC之后,堆释放的内存并没有归还给操作系统。
为什么没有归还给操作系统呢,是放入了内存分配器的内存池?还是commited部分的内存在使用后就会被操作系统一直统计?又或是其他原因
临时解决方法:限制一下Xmx吧(起码看起来使用的内存明显少了)。
java -Xmx1280M -jar xxx.jar
但还有一些其他状况,我在分析pmap -x统计的内存使用情况时,发现有很多内存的地址不在jcmd统计的native memory中,起初以为是direct memory,后来了解到这是JNI调用的本地方法自行申请的内存。
并且这些内存基本都是以64M大小划分的,查阅资料最后了解到了glibc的thread arena,glibc在2.11之后对每个线程引入了内存池。
设置环境变量MALLOC_ARENA_MAX=1关掉thread arena机制,这些以64M划分的内存区域确实没了,但反而在别处多出来两块使用内存特别高的区域,还有别的内存池机制?
服务启动时追踪一下系统调用,看看内存池是给哪些线程分配的。
strace -f -e"brk,mmap,munmap" -p <pid>
和jstack线程栈快照比对,追踪到了C1 Compiler Thread和C2 Compiler Thread头上,也确实开启了很多这样的线程,用JVM参数降低他们的数量。
java -Xmx1280M -XX:CICompilerCount=2 -jar xxx.jar
再用pmap -x查看,64M内存区域少了很多,进程RES也减了很多(起码看起来减了很多)。
内存分配器的内存池机制,导致操作系统会额外统计很多的内存使用,建议给Xmx设置一个合理值,glibc内存分配器的Thread Arena机制,也会占用很多内存。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。