假设您使用的是顶部的“VIRT”列,您无法从该数字推断出您的 Python 对象数量正在增长,或者至少增长到足以解释虚拟地址空间的总大小的假设。
例如,您是否知道 python 在其线程代码下使用 pthreads?这很重要,因为 pthreads 将“ulmimit -s”* 1K 的值作为默认堆栈大小。因此,python 中任何新线程的默认堆栈大小在某些 Linux 变体上通常为 8MB 甚至 40MB,除非您明确更改进程父进程中的“ulimit -s”值。许多服务器应用程序是多线程的,因此即使有 2 个额外的线程也会导致比您从 Pymler 输出中显示的更多的虚拟内存。您必须知道进程中有多少线程以及默认堆栈大小是多少,才能了解线程对总 VIRT 值的影响。
另外,在它自己的分配机制下,python 混合使用了 mmap 和 malloc。在 malloc 的情况下,如果程序是多线程的,在 linux 上 libc malloc 将使用多个 arenas,它们一次保留 64MB 范围(称为堆),但只使这些堆的开头可读可写并留下尾部在需要内存之前无法访问这些范围。那些不可访问的尾部通常很大,但就进程的提交内存而言根本不重要,因为不可访问的页面不需要任何物理内存或交换空间。尽管如此,“VIRT”中的顶部计数整个范围,包括范围开头的可访问开始和范围末尾的不可访问开始。
例如,考虑一个相当小的 python 程序,其中主线程启动了 16 个附加线程,每个线程在 python 分配中不使用太多内存:
import threading
def spin(seed):
l = [ i * seed for i in range(64) ]
while True:
l = [ i * i % 97 for i in l ]
for i in range(16):
t = threading.Thread(target=spin, args=[i])
t.start()
我们不希望该程序产生这么大的进程,但这是我们在顶部看到的,仅查看一个进程,它显示了超过 1GB 的 VIRT:
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu(s): 1.0%us, 1.9%sy, 3.3%ni, 93.3%id, 0.5%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 264401648k total, 250450412k used, 13951236k free, 1326116k buffers
Swap: 69205496k total, 17321340k used, 51884156k free, 104421676k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9144 tim 20 0 1787m 105m 99m S 101.8 0.0 13:40.49 python
我们可以通过获取正在运行的程序的实时内核(例如使用 gcore)并使用开源 chap 打开生成的内核,从而了解此类程序(以及您的程序)的高 VIRT 值可以在https://github.com/vmware/chap 找到并运行如下所示的命令:
chap> summarize writable
17 ranges take 0x2801c000 bytes for use: stack
14 ranges take 0x380000 bytes for use: python arena
16 ranges take 0x210000 bytes for use: libc malloc heap
1 ranges take 0x1a5000 bytes for use: libc malloc main arena pages
11 ranges take 0xa9000 bytes for use: used by module
1 ranges take 0x31000 bytes for use: libc malloc mmapped allocation
2 ranges take 0x7000 bytes for use: unknown
62 writable ranges use 0x28832000 (679,682,048) bytes.
从上面我们可以看出,最大的单次使用内存是17个栈。如果我们使用“describe writable”,我们会看到其中 16 个堆栈每个占用 40MB,这完全是因为我们忽略了将堆栈大小显式调整为更合理的值。
我们可以对不可访问(不可读取、不可写入或可执行)区域执行类似的操作,并看到 16 个“libc malloc 堆尾保留”范围占用了将近 1GB 的 VIRT。事实证明,这对于进程的已提交内存并不重要,但它确实对 VIRT 数做出了相当可怕的贡献。
chap> summarize inaccessible
16 ranges take 0x3fdf0000 bytes for use: libc malloc heap tail reservation
11 ranges take 0x15f9000 bytes for use: module alignment gap
16 ranges take 0x10000 bytes for use: stack overflow guard
43 inaccessible ranges use 0x413f9000 (1,094,684,672) bytes.
之所以有 16 个这样的范围,是因为每个旋转的线程都在重复进行分配,这导致 libc malloc(因为它被 python 分配器使用)在后台运行,从而划分出 16 个竞技场。
您可以对只读内存执行类似的命令(“summarize readonly”),或者您可以通过将“summarize”更改为“describe”来获取任何命令的更多详细信息。
我不能确切地说当您在自己的服务器上运行它时会发现什么,但是 REST 服务器似乎很可能是多线程的,所以我猜这将是一个不平凡的贡献到 TOP 显示的号码。
这仍然不能解释为什么这个数字会继续攀升,但如果您查看这些数字,您就可以知道下一步该往哪里看。例如,在上面的摘要中,由 python 处理而不使用 mmap 的分配不会占用那么多内存,只有 3.5MB:
14 ranges take 0x380000 bytes for use: python arena
在您的情况下,数字可能更大,因此您可以尝试以下任一方法:
describe used
describe free
summarize used
summarize free
请注意,上述命令还将涵盖本机分配(例如由共享库完成的分配)以及 python 分配。
在您自己的程序中执行类似的步骤应该可以让您更好地了解您所看到的那些“顶级”数字。