如何确定进程的“真实”内存使用情况,即私有脏RSS?
如何确定进程的“真实”内存使用情况,即私有脏RSS?
像 \'ps\' 和 \'top\' 这样的工具报告各种内存使用情况,例如 VM 大小和 Resident Set Size。然而,这些都不是“真正”的内存使用情况:
- 程序代码在同一程序的多个实例之间共享。
- 共享库程序代码在使用该库的所有进程之间共享。
- 有些应用程序会分叉出进程并与它们共享内存(例如通过共享内存段)。
- 虚拟内存系统使得 VM 大小的报告几乎无用。
- 当进程被交换出时,RSS 为 0,使其不太有用。
- 等等。
我发现在 Linux 中报告的私有脏读取 RSS 是最接近“真正”内存使用的东西。这可以通过在 /proc/somepid/smaps
中对所有 Private_Dirty
值求和来获得。
然而,其他操作系统是否提供类似的功能?如果没有,有什么替代方案?特别是,我对 FreeBSD 和 OS X 感兴趣。
在OSX上,活动监视器实际上可以很好地猜测内存的使用情况。
私有内存肯定是应用程序专用的内存。例如,堆栈内存和使用malloc()和可比较的函数/方法(Objective-C的alloc方法)动态保留的所有内存都是私有内存。如果您分叉,私有内存将与子进程共享,但标记为写时复制。这意味着只要一个页面没有被任一进程修改(父进程或子进程),它就会在它们之间共享。一旦任何进程修改了任何页面,此页面在修改之前将被复制。即使在使用分叉子进程共享此内存(它只能与分叉子进程共享)时,它仍然显示为“私有”内存,因为在最坏的情况下,它的每个页面都将被修改(迟早),然后它又会成为每个进程的私有内存。
共享内存是当前共享的内存(相同页面在不同进程的虚拟进程空间中可见)或将来可能变为共享的内存(例如只读内存,因为没有理由不共享只读内存)。至少这是我读过Apple某些命令行工具的源代码的理解。因此,如果您使用mmap(或将同一内存映射到多个进程的其他可比较调用)在进程之间共享内存,则这将是共享内存。然而,可执行代码本身也是共享内存,因为如果启动另一个应用程序实例,则没有理由为什么它不能共享已经加载到内存中的代码(可执行代码页面默认为只读,除非您在调试器中运行应用程序)。因此,共享内存实际上是应用程序使用的内存,就像私有内存一样,但它可能会额外与另一个进程共享(或者可能不会,但如果共享,为什么不计算到应用程序中呢?)
实际内存是当前分配给你的进程的RAM的数量,无论是私有还是共享的。这可能恰好是私有和共享的总和,但通常不是。你的进程可能被分配比它现在需要的内存更多(这加快了未来请求更多内存的速度),但对系统没有损失。如果另一个进程需要内存而没有可用的空闲内存,在系统开始交换之前,它会从你的进程中拿走额外的内存并将其分配给另一个进程(这是一个快速且无痛的操作);因此,你的下一个malloc调用可能会有些慢。实际内存也可能小于私有和物理内存;这是因为如果你的进程从系统请求内存,它只会接收到“虚拟内存”。只要你不使用它(所以分配10 MB内存,只使用其中一个字节,你的进程只会分配4096字节的单个页面内存-如果你实际上需要它,剩余的内存才会分配)。进一步交换的内存也可能不计入实际内存(对此不确定),但它将计入共享和私有内存。
虚拟内存是您应用程序进程空间中被视为有效的所有地址块的总和。这些地址可能链接到物理内存(再次是私有或共享的),也可能不是,但在这种情况下,它们将在您使用地址时链接到物理内存。访问已知地址范围之外的内存地址将导致SIGBUS,并导致应用程序崩溃。当内存被交换时,此内存的虚拟地址空间仍然有效,并且访问这些地址会导致内存被交换回来。
结论:
如果您的应用程序没有显式或隐式使用共享内存,则私有内存是由于堆栈大小(如果多线程,则为多个堆栈大小)和因为您为动态内存分配所做的malloc()调用而需要的内存量。在这种情况下,您不必过多关注共享或实际内存。
如果您的应用程序使用共享内存,其中包括图形用户界面,例如在您的应用程序和WindowServer之间共享内存,那么您也可以查看共享内存。非常高的共享内存数字可能意味着您在某个时刻将太多的图形资源加载到内存中。
真实内存对应用程序开发来说并不是很重要。如果它比共享和私有的总和要大,则除此以外,这意味着系统懒得从您的进程中取走内存。如果它更小,则您的进程请求了比实际所需更多的内存,这也不是坏事,因为只要您不使用所有请求的内存,您就不会“窃取”系统的内存。如果它远小于共享和私有的总和,则您只需要在可能的情况下请求更少的内存,因为您过度请求内存(再次声明,这不是坏事,但它告诉我,您的代码不针对最小内存使用量进行优化,如果它是跨平台的,其他平台可能没有这样一个成熟的内存处理,因此您可能更喜欢分配许多小块而不是几个大块,或者更早地释放内存等)。
如果您对所有这些信息仍然不满意,您可以获取更多信息。打开终端并运行:
sudo vmmap
其中,是您进程的进程ID。这将为您显示进程空间中每个内存块的统计信息,包括起始和结束地址。它还将告诉您这个内存来自哪里(映射文件?栈内存?Malloc'ed内存?可执行文件的__DATA或__TEXT部分?),它的大小是多少KB,访问权限以及它是私有的、共享的还是写时复制的。如果它是从文件映射的,它甚至会给您文件的路径。
如果您只想要“实际” RAM 使用情况,请使用
sudo vmmap -resident
现在它将显示每个内存块的虚拟大小以及其中多少实际上当前存在于物理内存中。
每个转储的末尾还有一个概述表,列出了不同内存类型的总和。这个表在我的系统上看起来像这样:
REGION TYPE [ VIRTUAL/RESIDENT] =========== [ =======/========] ATS (font support) [ 33.8M/ 2496K] CG backing stores [ 5588K/ 5460K] CG image [ 20K/ 20K] CG raster data [ 576K/ 576K] CG shared images [ 2572K/ 2404K] Carbon [ 1516K/ 1516K] CoreGraphics [ 8K/ 8K] IOKit [ 256.0M/ 0K] MALLOC [ 256.9M/ 247.2M] Memory tag=240 [ 4K/ 4K] Memory tag=242 [ 12K/ 12K] Memory tag=243 [ 8K/ 8K] Memory tag=249 [ 156K/ 76K] STACK GUARD [ 101.2M/ 9908K] Stack [ 14.0M/ 248K] VM_ALLOCATE [ 25.9M/ 25.6M] __DATA [ 6752K/ 3808K] __DATA/__OBJC [ 28K/ 28K] __IMAGE [ 1240K/ 112K] __IMPORT [ 104K/ 104K] __LINKEDIT [ 30.7M/ 3184K] __OBJC [ 1388K/ 1336K] __OBJC/__DATA [ 72K/ 72K] __PAGEZERO [ 4K/ 0K] __TEXT [ 108.6M/ 63.5M] __UNICODE [ 536K/ 512K] mapped file [ 118.8M/ 50.8M] shared memory [ 300K/ 276K] shared pmap [ 6396K/ 3120K]
这告诉我们什么?例如,Firefox 二进制文件和它加载的所有库在他们的__TEXT部分中共有 108 MB 的数据,但目前只有 63 MB 的数据实际上驻留在内存中。字体支持(ATS)需要 33 MB,但实际上只有大约 2.5 MB 的数据在内存中。它使用超过 5 MB 的 CG 后备存储器,CG = Core Graphics,这很可能是窗口内容、按钮、图像和其他为了快速绘制而缓存的数据。它通过 malloc 调用请求了 256 MB,并且目前有 247 MB 实际上在映射到内存页中。它留出了 14 MB 的堆栈空间,但目前仅使用了 248 KB 的堆栈空间。
vmmap还有一个好的总结在表格之上
ReadOnly portion of Libraries: Total=139.3M resident=66.6M(48%) swapped_out_or_unallocated=72.7M(52%) Writable regions: Total=595.4M written=201.8M(34%) resident=283.1M(48%) swapped_out=0K(0%) unallocated=312.3M(52%)
这显示了OS X的一个有趣方面:对于来自库的只读内存,无论它是否被交换出去或者是否被简单退回,它的角色都是相同的。只有当内存是可写的时,这才有所不同(在我的案例中,所有请求内存的52%从未被使用,因此未分配,因为0%的内存已交换到磁盘中)。
原因很简单:来自映射文件的只读内存不会被交换。如果系统需要内存,当前页面将从进程中简单地删除,因为内存已经被“交换”了。它只包含直接从文件映射的内容,只要文件仍然存在,就可以随时重新映射这些内容。这样内存也不会浪费交换文件的空间。只有可写内存必须先交换到文件中,然后才能放弃,因为其内容以前没有存储在磁盘上。