在Mac OSX中,活动监视器实际上可以很好地猜测。
私有内存肯定是只被您的应用程序使用的内存。例如,堆栈内存和所有使用malloc()和类似函数/方法(Objective-C的alloc方法)动态保留的内存是私有内存。如果您进行分叉,则私有内存将与您的子进程共享,但标记为写时复制。这意味着只要一个页面未被任何进程(父进程或子进程)修改,它就会在它们之间共享。一旦任何一个进程修改了任何页面,该页面将在修改之前被复制。即使这个内存与fork子进程共享(并且只能与fork子进程共享),它仍然显示为“private”内存,因为在最坏的情况下,它的每一页都会被修改(迟早),然后它再次对于每个进程都是私有的。
共享内存是当前共享的内存(相同的页面在不同进程的虚拟进程空间中可见)或者未来可能共享的内存(例如只读内存,因为没有理由不共享只读内存)。至少这是我从苹果公司的一些命令行工具的源代码中读到的。因此,如果您使用mmap(或将相同内存映射到多个进程的类似调用)在进程之间共享内存,这将是共享内存。但可执行代码本身也是共享内存,因为如果启动另一个应用程序实例,则没有理由它不会共享已经加载到内存中的代码(可执行代码页面默认情况下是只读的,除非您在调试器中运行应用程序)。因此,共享内存实际上是您的应用程序使用的内存,就像私有内存一样,但可能还与另一个进程共享(或者可能不共享,但如果它被共享,为什么不算作您的应用程序的一部分?)
实际内存是当前“分配”给您的进程的RAM量,无论是私有的还是共享的。这可能恰好等于私有和共享的总和,但通常不是这样。您的进程可能被分配比它当前所需的更多的内存(这样可以加快未来请求更多内存的速度),但这对系统来说并不是损失。如果另一个进程需要内存而没有可用的空闲内存,在系统开始交换之前,它将从您的进程中拿走这些额外的内存,并将其分配给另一个进程(这是一个快速且无痛的操作);因此,您的下一个malloc调用可能会稍慢一些。实际内存也可能比私有和物理内存小;这是因为如果您的进程从系统请求内存,它将只收到“虚拟内存”。只要您不使用它(因此malloc 10 MB的内存,只使用其中一个字节,您的进程将被分配一个单独页面,4096字节的内存-其余仅在您实际需要时分配)。交换的内存也可能不计入实际内存(对此不确定),但它将计入共享和私有内存。
虚拟内存是指在应用程序进程空间中被认为有效的所有地址块的总和。这些地址可能与物理内存链接(再次是私有或共享),也可能没有,但是在这种情况下,只要使用该地址,它们就会与物理内存链接。访问已知地址范围之外的内存地址将导致SIGBUS错误并使应用程序崩溃。当内存被交换时,该内存的虚拟地址空间仍然有效,并且访问这些地址将导致内存被重新交换回来。
结论:
如果您的应用程序不明确或隐式地使用共享内存,则私有内存是由于堆栈大小(或多线程的大小)以及由于您为动态内存分配而进行过的malloc()调用所需的内存量。在这种情况下,您不必过多关心共享内存或实际内存。
如果您的应用程序使用共享内存,包括图形用户界面,在此类情况下,内存在应用程序和WindowServer之间共享,那么您也可以查看共享内存。非常高的共享内存数字可能意味着您当前在内存中加载了太多图形资源。
实际内存对应用程序开发没有太大兴趣。如果它比共享和私有内存的总和更大,那么这意味着系统对从您的进程中取走内存有点懒惰。如果它更小,则意味着您的进程请求了比实际需要的更多的内存,这也不是坏事,因为只要您没有使用所有请求的内存,就不会“窃取”系统的内存。如果它远小于共享和私有内存的总和,您可能只需要在可能的情况下请求更少的内存,因为您有点过度请求内存(再次说明,这不是坏事,但这告诉我,您的代码未经过优化,以实现最小内存使用量,如果它跨平台,其他平台可能没有如此复杂的内存处理,因此您可能更喜欢分配许多小块而不是几个大块,或者更早地释放内存等)。
如果您对所有这些信息仍不满意,可以获得更多信息。打开终端并运行:
sudo vmmap <pid>
其中,pid
是您进程的进程 ID。这将向您显示进程空间中每个内存块的统计信息,包括起始地址和结束地址。它还会告诉您此内存来自哪里(映射文件?堆栈内存?malloc分配的内存?可执行文件的__DATA或__TEXT部分?),以 KB 为单位的大小,访问权限以及它是私有的、共享的还是写时复制的。如果它是从文件映射而来,它甚至会给出文件的路径。
如果您只想要“实际”的 RAM 使用情况,请使用
sudo vmmap -resident <pid>
现在它将显示每个内存块的虚拟大小和实际存在于物理内存中的大小。
每个转储结束时还会有一个概述表格,列出了不同内存类型的总和。这张表格在我的系统上现在看起来像这样:
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% 的内存被交换到磁盘上)。
原因很简单:来自映射文件的只读内存不会被交换出去。如果系统需要该内存,当前的页面将被从进程中删除,因为内存已经“被交换”。它只由直接从文件映射的内容组成,每当需要时,这些内容可以重新映射,因为文件仍然存在。这样,这些内存也不会在交换文件中浪费空间。只有可写内存在被删除之前必须先被交换到文件中,因为它的内容之前并没有存储在磁盘上。