携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
昨天提到的内存泄漏问题,今天早上我还在那里看了半天,以为真的是自己代码哪里出了问题。排查一遍之后,下午请教了一个前辈,我跟他说内存泄漏的根据就是 VIRT 太大,他跟我说不算是内存泄漏,代码没问题。简单告诉我原因,回来之后又查了查资料,似懂非懂。下面总结一下原因。
要说 VIRT 过高,就要先知道 VIRT 是从哪来的,它是干什么的?为什么会过高?而了解 VIRT 的前提是,要知道 Top 命令。
Top 命令
Top 命令监控某个进程的资源占有情况。Top 之后,可以看到每个进程的资源占有情况,有关内存的主要参数就是下面几个。
VIRT(Virtual memory usage)
1、进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据等;
2、加入进程申请 100m 的内存,但实际只使用了 10m,那么它还是会增长 100m,而不是实际使用的大小。
RES(Resident memory usage)
1、进程当前使用的内存大小,但不包括 swap out(换出,新策略拒绝而就策略准入;与之对应的是新策略准入而旧策略拒绝的——换入,swap in);
2、包含其他进程的共享;
3、如果申请 100m 的内存,但实际只使用了 10m,则 RES 只增长 10m,与 VIRT 相反;
4、关于库占用内存的情况,它只统计加载的库文件所占内存大小。
SHR(Shared memory)
1、除了自身进程的共享内存,也包括其他进程的共享内存;
2、虽然进程只使用了几个共享库的函数,但它包含了整个共享库的大小;
3、计算某个进程所占的物理内存大小公式:RES - SHR;
4、swap out 后,SHR 会降下来。
DATA
1、数据占用的内存。如果 Top 之后没显示,按 R 键可以看到;
2、真正的该程序要求的数据空间,是真正在运行中要使用的。
上面介绍了一下 Top 之后某个进程的内存情况,其中就包含 VIRT,那 VIRT 究竟是什么意思呢?别急,马上解释 :)
什么是 VIRT?
现如今的操作系统,其分配虚拟地址空间操作不同分配物理内存。在 64 位操作系统上,可用的最大虚拟地址空间有 16 EB,即大约 180 亿GB(1 EB = 1024 TB = 1048576 GB)。所以一台只有 16 GB 物理内存的机器上,也可以获取 4 TB 的地址空间来使用。而如果 VIRT 显示非常大,它只是表示使用了这么多的内存空间地址而已,并非真正使用了这么多的虚拟内存。
那么为什么 Java 程序会用到这么多的地址空间呢?
通过“pmap -x”命令查看可以发现有很多奇怪的64MB的内存映射。查资料发现这是 glibc 在版本 2.10 引入的 arena 新功能导致。CentOS 6/7 的 glibc 大都是 2.12/ 2.17 了,所以都会有这个问题。这个功能对每个线程都分配一个分配一个本地 arena 来加速多线程的执行。
同时由于 Java 自身的特性,Java 程序由于自己维护堆的使用,导致调用 glibc 去管理内存的次数较少。甚至,由于 Java 8 开始使用 metaspace 原空间取代永久代,而元空间是存放在操作系统本地内存中,那线程一多,每个线程都要使用一点元空间,每个线程都分配一个 arena,每个都64MB,就会导致巨大的虚拟地址被分配。
总之,一切疑似内存泄漏的根据就是这个 VIRT 太大,而 VIRT 过高,并不是内存泄露的表现。VIRT 太大是因为分配了太多的地址空间导致的,一般来讲不用太在意 VIRT 过高,因为有 16 EB 的空间可以使用。(如果确实需要控制 VIRT 的使用,可以通过设置环境变量 MALLOC_ARENA_MAX 的值来控制 VIRT 资源的使用。)就是因为 glibc 的版本,又加上 Java 8 的特性,导致了 VIRT 过高,不算是内存泄漏。
能力有限,可能文章中有不对的地方,还请各位海涵,有错误,望指正。
我向你敬礼啊,Salute!