57

'ps' 和 'top' 之类的工具会报告各种内存使用情况,例如 VM 大小和驻留集大小。但是,这些都不是“真正的”内存使用:

  • 程序代码在同一程序的多个实例之间共享。
  • 共享库程序代码在使用该库的所有进程之间共享。
  • 一些应用程序分叉进程并与它们共享内存(例如,通过共享内存段)。
  • 虚拟内存系统使 VM 大小报告几乎毫无用处。
  • 当进程被换出时,RSS 为 0,因此它不是很有用。
  • 等等等等。

我发现 Linux 报告的私有脏 RSS 是最接近“真实”内存使用的东西。这可以通过将 中的所有Private_Dirty值相加来获得/proc/somepid/smaps

但是,其他操作系统是否提供类似的功能?如果没有,有什么替代方案?特别是,我对 FreeBSD 和 OS X 很感兴趣。

4

10 回答 10

49

在 OSX 上,活动监视器实际上给你一个很好的猜测。

私有内存肯定是仅由您的应用程序使用的内存。例如堆栈内存和所有使用 malloc() 和类似函数/方法(Objective-C 的分配方法)动态保留的内存都是私有内存。如果你分叉,私人内存将与你的孩子共享,但标记为写时复制。这意味着只要页面没有被任何一个进程(父进程或子进程)修改,它就会在它们之间共享。只要任一进程修改任何页面,该页面就会在被修改之前被复制。即使此内存与 fork 子共享(并且只能与 fork 子共享),它仍然显示为“私有”内存,因为在最坏的情况下,它的每一页都会被修改(迟早)并且然后它再次对每个进程都是私有的。

共享内存要么是当前共享的内存(相同的页面在不同进程的虚拟进程空间中可见),要么是将来可能共享的内存(例如只读内存,因为没有理由不共享 read -仅记忆)。至少我是这样从 Apple 读取一些命令行工具的源代码的。因此,如果您使用 mmap(或将相同内存映射到多个进程的类似调用)在进程之间共享内存,这将是共享内存。然而,可执行代码本身也是共享内存,因为如果您的应用程序的另一个实例启动,它没有理由不共享已加载到内存中的代码(可执行代码页默认为只读,除非您正在运行您的调试器中的应用程序)。因此,共享内存实际上是您的应用程序使用的内存,

实际内存是当前“分配”给您的进程的 RAM 量,无论是私有的还是共享的。这可以恰好是私有和共享的总和,但通常不是。您的进程分配给它的内存可能比它当前需要的多(这会加快将来对更多内存的请求),但这不会对系统造成任何损失。如果另一个进程需要内存并且没有可用内存,在系统开始交换之前,它会从你的进程中取出多余的内存并分配给另一个进程(这是一个快速而轻松的操作);因此,您的下一次 malloc 调用可能会慢一些。实际内存也可以小于私有和物理内存;这是因为如果您的进程向系统请求内存,它只会收到“虚拟内存”。只要您不使用此虚拟内存,它就不会链接到任何实际内存页面(因此 malloc 10 MB 内存,仅使用其中一个字节,您的进程将只获得一个分配的内存页面,4096 字节- 其余部分仅在您真正需要时才分配)。交换的更多内存也可能不计入实际内存(对此不确定),但它将计入共享和私有内存。

虚拟内存是您的应用程序进程空间中被认为有效的所有地址块的总和。这些地址可能链接到物理内存(同样是私有的或共享的),也可能不链接,但在这种情况下,一旦您使用该地址,它们就会链接到物理内存。访问已知地址之外的内存地址将导致 SIGBUS 并且您的应用程序将崩溃。交换内存时,该内存的虚拟地址空间保持有效,访问这些地址会导致内存被换回。

结论:
如果您的应用程序没有显式或隐式使用共享内存,则私有内存是您的应用程序所需的内存量,因为堆栈大小(或多线程大小)以及您为动态内存进行的 malloc() 调用。在这种情况下,您不必非常关心共享内存或真实内存。

如果您的应用程序使用共享内存,并且这包括一个图形 UI,例如,内存在您的应用程序和 WindowServer 之间共享,那么您可能也会查看共享内存。一个非常高的共享内存数量可能意味着您目前在内存中加载了太多的图形资源。

应用程序开发对真实内存没有多大兴趣。如果它大于共享和私有的总和,那么这意味着系统懒惰地从您的进程中取出内存。如果它更小,那么你的进程请求的内存比它实际需要的多,这也不错,因为只要你不使用所有请求的内存,你就不会从系统中“窃取”内存。如果它远小于共享和私有的总和,您可能只考虑在可能的情况下请求更少的内存,因为您有点过度请求内存(同样,这还不错,但它告诉我您的代码不是针对最小内存使用进行了优化,如果它是跨平台的,其他平台可能没有如此复杂的内存处理,

如果您仍然对所有这些信息不满意,您可以获得更多信息。打开终端并运行:

sudo vmmap <pid>

您的进程的进程 ID 在哪里。这将向您显示进程空间中每个内存块的统计信息以及开始和结束地址。它还会告诉您此内存来自哪里(映射文件?堆栈内存?Malloc 内存?可执行文件的 __DATA 或 __TEXT 部分?),它有多大(KB),访问权限以及它是否是私有的,共享或写时复制。如果它是从文件映射的,它甚至会为您提供文件的路径。

如果您只想“实际”使用 RAM,请使用

sudo vmmap -resident <pid>

现在它将为每个内存块显示该内存块实际上有多大,以及它当前实际存在于物理内存中的有多少。

在每个转储的末尾还有一个概述表,其中包含不同内存类型的总和。对于我的系统上的 Firefox,此表现在看起来像这样:

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% 的内存已被换出到磁盘)。

原因很简单:映射文件中的只读内存不会被交换。如果系统需要内存,则简单地从进程中删除当前页面,因为内存已经“交换”了。它仅包含直接从文件映射的内容,并且可以在需要时重新映射此内容,因为文件仍然存在。这样,该内存也不会浪费交换文件中的空间。只有可写内存在被删除之前必须首先交换到文件,因为它的内容之前没有存储在磁盘上。

于 2009-12-23T19:15:34.180 回答
10

在 Linux 上,您可能需要 /proc/self/smaps 中的 PSS(比例集大小)数字。映射的 PSS 是其 RSS 除以使用该映射的进程数。

于 2011-10-12T02:08:51.500 回答
7

Top知道如何做到这一点。它在 Debian Linux 上默认显示 VIRT、RES 和 SHR。VIRT = 交换 + RES。RES = 代码 + 数据。SHR 是可以与另一个进程(共享库或其他内存)共享的内存。

此外,“脏”内存只是已使用和/或尚未交换的 RES 内存。

这可能很难说,但最好的理解方法是查看一个没有交换的系统。那么,RES-SHR就是进程独占内存。但是,这不是一个很好的查看方式,因为您不知道 SHR 中的内存正在被另一个进程使用。它可能表示仅由进程使用的未写入的共享对象页面。

于 2008-09-23T01:57:15.273 回答
7

你真的不能。

我的意思是,进程之间的共享内存......你要数它吗?如果你不计算它,你就错了;所有进程的内存使用量的总和不会是总内存使用量。如果你数它,你将数它两次——总和不会是正确的。

我,我对 RSS 很满意。并且知道你不能完全依赖它......

于 2008-11-29T18:06:25.947 回答
6

您可以从 /proc/pid/smaps 获取私有脏和私有干净 RSS

于 2012-03-06T20:55:32.187 回答
4

看看smem。它会给你PSS信息

http://www.selenic.com/smem/

于 2014-03-10T16:26:15.413 回答
3

对其进行了重新设计,使其更加简洁,以展示 bash 中的一些适当的最佳实践,特别是使用awk而不是bc.

find /proc/ -maxdepth 1 -name '[0-9]*' -print0 | while read -r -d $'\0' pidpath; do
  [ -f "${pidpath}/smaps" ] || continue
  awk '!/^Private_Dirty:/ {next;}
       $3=="kB" {pd += $2 * (1024^1); next}
       $3=="mB" {pd += $2 * (1024^2); next}
       $3=="gB" {pd += $2 * (1024^3); next}
       $3=="tB" {pd += $2 * (1024^4); next}
       $3=="pB" {pd += $2 * (1024^5); next}
       {print "ERROR!!  "$0 >"/dev/stderr"; exit(1)}
       END {printf("%10d: %d\n", '"${pidpath##*/}"', pd)}' "${pidpath}/smaps" || break
done

在我机器上的一个方便的小容器上,| sort -n -k 2用于对输出进行排序,如下所示:

        56: 106496
         1: 147456
        55: 155648
于 2013-05-16T21:04:38.607 回答
2

使用 mincore(2) 系统调用。引用手册页:

DESCRIPTION
     The mincore() system call determines whether each of the pages in the
     region beginning at addr and continuing for len bytes is resident.  The
     status is returned in the vec array, one character per page.  Each
     character is either 0 if the page is not resident, or a combination of
     the following flags (defined in <sys/mman.h>):
于 2018-08-31T15:39:19.087 回答
1

对于一个提到 Freebsd 的问题,很惊讶还没有人写这个:

如果您想要 linux 风格的 /proc/PROCESSID/status 输出,请执行以下操作:

mount -t linprocfs none /proc
cat /proc/PROCESSID/status

至少在 FreeBSD 7.0 中,默认情况下没有安装(7.0 是一个更旧的版本,但是对于这个基本的东西,答案隐藏在邮件列表中!)

于 2013-01-07T07:31:35.527 回答
1

查看一下,这是 gnome-system-monitor 的源代码,它认为一个进程“真正使用info->mem”的内存是X Server Memory( info->memxserver) 和 Writable Memory( ) 的 sum( info->memwritable),“ Writable Memory ”是/proc/PID/smaps文件中标记为“ Private_Dirty ”的内存块。

除了 linux 系统,根据 gnome-system-monitor 代码可能会有不同的方式。

static void
get_process_memory_writable (ProcInfo *info)
{
    glibtop_proc_map buf;
    glibtop_map_entry *maps;

    maps = glibtop_get_proc_map(&buf, info->pid);

    gulong memwritable = 0;
    const unsigned number = buf.number;

    for (unsigned i = 0; i < number; ++i) {
#ifdef __linux__
        memwritable += maps[i].private_dirty;
#else
        if (maps[i].perm & GLIBTOP_MAP_PERM_WRITE)
            memwritable += maps[i].size;
#endif
    }

    info->memwritable = memwritable;

    g_free(maps);
}

static void
get_process_memory_info (ProcInfo *info)
{
    glibtop_proc_mem procmem;
    WnckResourceUsage xresources;

    wnck_pid_read_resource_usage (gdk_screen_get_display (gdk_screen_get_default ()),
                                  info->pid,
                                  &xresources);

    glibtop_get_proc_mem(&procmem, info->pid);

    info->vmsize    = procmem.vsize;
    info->memres    = procmem.resident;
    info->memshared = procmem.share;

    info->memxserver = xresources.total_bytes_estimate;

    get_process_memory_writable(info);

    // fake the smart memory column if writable is not available
    info->mem = info->memxserver + (info->memwritable ? info->memwritable : info->memres);
}
于 2013-09-15T17:19:49.497 回答