glibc 的 malloc per thread arenas 特性

本文准确性有待深究,只查阅过部分资料和进行过一些简单讨论,并未在代码层面进行过研究。如有纠错或补充,请在本文留言,感谢!

从 RHEL5 迁移到 RHEL6 的用户会有这样的发现:多线程程序在 RHEL6 中占用的*虚拟内存*,要比在 RHEL5 上的多。这种情况在 JAVA 程序中尤为突出,尽管 JAVA 应用是单线程的。
其中的一个原因,是 glibc 2.12 中的新特性 malloc per thread arenas 造成的。

Java 占用较多虚拟内存

通常,Java 应用会运行在 JVM 上。JVM 有自己的一套内存调用方式,一般不会使用到 glibc. 但某些对象还是会调用 malloc,从而使用使用到 per thread arenas 这个特性。

为何有 per thread arenas?

在过去的 malloc 实现中,每个程序会有一个 main arenas 供 malloc 申请使用。对于多线程的程序,每个线程调用 malloc 的时候,都需要事先检查是否有锁,确认没有锁后,才能拿到内存空间。这影响了程序的性能。

在现在的实现中,每一个线程都会有自己的 arenas,这就避免了线程之间的竞争,从而提高性能。

另外,我们可以看到 arenas 是一些大约为 64MB 的无权限匿名页(64位系统中)。

更多的资源占用?

虚拟内存或者说地址空间,都只是操作系统层面的概念,只有真正被使用到了,才会实际占用物理内存。所以对于程序来说,以几乎不用的成本的地址空间换取性能提高,是值得的。

在 RHEL5/Centos5 中开启 per thread arenas 功能

在 RHEL5/Centos5 中, glibc 添加了 per thread areans 功能,可以通过 MALLOC_PER_THREAD 环境变量开启。

RHEL5 默认未开启:

[root@rhel511mlocate ~]# java ReadString

[root@rhel511mlocate ~]# ps auxwww | grep ReadString | grep -v grep
root     10709  0.7  1.5 850132 15760 pts/0    Sl+  15:19   0:00 java ReadString

[root@rhel511mlocate ~]# pmap -x 10709 | sort -nk 2 | grep anon
(......)
00000000f5a00000   21248    2772    2772 rw---    [ anon ]
00002ad7fee1c000   46656       0       0 rw---    [ anon ]
00000000f6ec0000  148736       0       0 rw---    [ anon ]
00000000d6930000  166016       0       0 rw---    [ anon ]
00000000e15d0000  331968       0       0 rw---    [ anon ]

RHEL5 中开启 per thread arenas:

[root@rhel511mlocate ~]# export MALLOC_PER_THREAD=1; java ReadString

[root@rhel511mlocate ~]# ps auxwww | grep ReadString | grep -v grep
root     10807  0.4  1.5 1373540 15788 pts/0   Sl+  15:30   0:00 java ReadString

[root@rhel511mlocate ~]# pmap -x 10807 | sort -nk 2 | grep anon
(......)
00000000f5a00000   21248    2772    2772 rw---    [ anon ]
00002b8028270000   46656       0       0 rw---    [ anon ]
00002b80240df000   64644       0       0 -----    [ anon ]  <---
00002b802c021000   65404       0       0 -----    [ anon ]  <---
00002b8030021000   65404       0       0 -----    [ anon ]  <---
00002b8038021000   65404       0       0 -----    [ anon ]  <---
00002b803c021000   65404       0       0 -----    [ anon ]  <---
00002b8040021000   65404       0       0 -----    [ anon ]  <---
00002b8044021000   65404       0       0 -----    [ anon ]  <---
00002b8048021000   65404       0       0 -----    [ anon ]  <---
00000000f6ec0000  148736       0       0 rw---    [ anon ]
00000000d6930000  166016       0       0 rw---    [ anon ]
00000000e15d0000  331968       0       0 rw---    [ anon ]

在 RHEL6/Centos6 中禁用 per thread arenas 功能

在 RHEL6, 我们可以设置最大 arenas 数为1,来达到 RHEL5 中只有 main arena 的效果。 RHEL6 默认情况下,最大 arenas 数目为 cpu core * 8.

RHEL6 默认:

[root@RHEL66mlocate ~]# java ReadString

[root@RHEL66mlocate ~]# ps auxwww | grep ReadString | grep -v grep
root      1497  0.4  2.0 1413032 20456 pts/0   Sl+  15:58   0:00 java ReadString

[root@RHEL66mlocate ~]# pmap -x 1497 | sort -nk 2 | grep anon
(......)
00000000e0ca0000   10688       0       0 rw---    [ anon ]
00000000f5a00000   21248    4096    4096 rw---    [ anon ]
00007fef79270000   47528     756     756 rw---    [ anon ]
00007fef7c0da000   64664       0       0 -----    [ anon ]  <---
00007fef50021000   65404       0       0 -----    [ anon ]  <---
00007fef58021000   65404       0       0 -----    [ anon ]  <---
00007fef5c021000   65404       0       0 -----    [ anon ]  <---
00007fef60021000   65404       0       0 -----    [ anon ]  <---
00007fef6c021000   65404       0       0 -----    [ anon ]  <---
00007fef70021000   65404       0       0 -----    [ anon ]  <---
00007fef74021000   65404       0       0 -----    [ anon ]  <---
00000000f6ec0000  148736       0       0 rw---    [ anon ]
00000000d6b30000  165312       0       0 rw---    [ anon ]
00000000e1710000  330688       0       0 rw---    [ anon ]

RHEL6 设置 MALLOC_ARENA_MAX=1:

[root@RHEL66mlocate ~]# export MALLOC_ARENA_MAX=1; java ReadString

[root@RHEL66mlocate ~]# ps auxwww | grep ReadString | grep -v grep
root      1538  0.2  2.0 889544 20616 pts/0    Sl+  16:07   0:00 java ReadString

[root@RHEL66mlocate ~]# pmap -x 1538 | sort -nk 2 | grep anon
(......)
00000000e0ca0000   10688       0       0 rw---    [ anon ]
00000000f5a00000   21248    4096    4096 rw---    [ anon ]
00007f4959818000   46656       0       0 rw---    [ anon ]
00000000f6ec0000  148736       0       0 rw---    [ anon ]
00000000d6b30000  165312       0       0 rw---    [ anon ]
00000000e1710000  330688       0       0 rw---    [ anon ]

RHEL6 设置 MALLOC_ARENA_MAX=2:

[root@RHEL66mlocate ~]# export MALLOC_ARENA_MAX=2; java ReadString

[root@RHEL66mlocate ~]# ps auxwww | grep ReadString | grep -v grep
root      1554  0.3  2.0 954280 20608 pts/0    Sl+  16:13   0:00 java ReadString

[root@RHEL66mlocate ~]# pmap -x 1554 | sort -nk 2 | grep anon
(......)
00007fd50ff5f000    1540     772     772 rw---    [ anon ]
00007fd5141ac000    2496    2240    2240 rwx--    [ anon ]
00000000d6600000    5312    2048    2048 rw---    [ anon ]
00000000e0ca0000   10688       0       0 rw---    [ anon ]
00000000f5a00000   21248    4096    4096 rw---    [ anon ]
00007fd51441c000   46656       0       0 rw---    [ anon ]
00007fd5100e0000   64640       0       0 -----    [ anon ]  <---
00000000f6ec0000  148736       0       0 rw---    [ anon ]
00000000d6b30000  165312       0       0 rw---    [ anon ]
00000000e1710000  330688       0       0 rw---    [ anon ]

参考文档

[ Malloc per-thread arenas in glibc ]
http://journal.siddhesh.in/posts/malloc-per-thread-arenas-in-glibc.html