确定进程“真实”内存使用情况的方法,即私人脏 RSS?
- 2024-10-29 08:35:00
- admin 原创
- 48
问题描述:
诸如“ps”和“top”之类的工具会报告各种内存使用情况,例如虚拟机大小和驻留集大小。但是,这些都不是“实际”内存使用情况:
程序代码在同一程序的多个实例之间共享。
共享库程序代码在使用该库的所有进程之间共享。
一些应用程序分叉进程并与它们共享内存(例如通过共享内存段)。
虚拟内存系统使得虚拟机大小报告几乎毫无用处。
当一个进程被换出时,RSS 为 0,因此它不是很有用。
等等等等。
我发现 Linux 报告的私有脏 RSS 最接近“真实”内存使用情况。这可以通过将Private_Dirty
中的所有值相加来获得/proc/somepid/smaps
。
但是,其他操作系统是否提供类似的功能?如果没有,有哪些替代方案?特别是,我对 FreeBSD 和 OS X 感兴趣。
解决方案 1:
在 OSX 上,活动监视器实际上会给你一个非常好的猜测。
私有内存肯定是仅由您的应用程序使用的内存。例如,堆栈内存和使用 malloc() 和类似函数/方法(Objective-C 的 alloc 方法)动态保留的所有内存都是私有内存。如果您分叉,私有内存将与您的子进程共享,但标记为写时复制。这意味着只要页面未被任何进程(父进程或子进程)修改,它就会在它们之间共享。只要任何一个进程修改任何页面,该页面就会在修改之前被复制。即使此内存与分叉子进程共享(并且只能与分叉子进程共享),它仍显示为“私有”内存,因为在最坏的情况下,它的每一页都将被修改(迟早),然后它再次对每个进程都是私有的。
共享内存要么是当前共享的内存(相同的页面在不同进程的虚拟进程空间中可见),要么是将来可能共享的内存(例如只读内存,因为没有理由不共享只读内存)。至少这是我阅读 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% 的内存已换出到磁盘)。
原因很简单:映射文件中的只读内存不会被交换。如果系统需要内存,则当前页面会从进程中删除,因为内存已经“交换”了。它仅由直接从文件映射的内容组成,并且可以在需要时重新映射此内容,因为文件仍在那里。这样,这块内存也不会浪费交换文件中的空间。只有可写内存必须先交换到文件,然后才能删除,因为其内容之前未存储在磁盘上。
解决方案 2:
在 Linux 上,您可能需要 /proc/self/smaps 中的 PSS(比例集大小)数字。映射的 PSS 是其 RSS 除以使用该映射的进程数。
解决方案 3:
你确实不能。
我的意思是,进程之间的共享内存...你要计算它吗?如果你不计算它,你就错了;所有进程的内存使用量的总和不会是总内存使用量。如果你计算它,你将计算两次 - 总和将不正确。
我对 RSS 很满意。但我知道你不能完全依赖它...
解决方案 4:
Top 知道如何做到这一点。它在 Debian Linux 上默认显示 VIRT、RES 和 SHR。VIRT = SWAP + RES。RES = CODE + DATA。SHR 是可能与另一个进程共享的内存(共享库或其他内存)。
此外,“脏”内存仅仅是已使用过和/或尚未交换的 RES 内存。
这可能很难说,但最好的理解方式是查看未进行交换的系统。然后,RES - SHR 是进程独占内存。但是,这不是一个好的看待它的方式,因为您不知道 SHR 中的内存正在被另一个进程使用。它可能代表仅由进程使用的未写入共享对象页面。
解决方案 5:
看看 smem。它将为您提供 PSS 信息
解决方案 6:
您可以从 /proc/pid/smaps 获取私人脏 RSS 和私人干净 RSS
解决方案 7:
重新设计这个以使其更加清晰,以展示 bash 中的一些适当的最佳实践,特别是使用awk
而不是bc
。
find /proc/ -maxdepth 1 -name '[0-9]*' -print0 | while read -r -d $' ' 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
", '"${pidpath##*/}"', pd)}' "${pidpath}/smaps" || break
done
在我的机器上一个方便的小容器中,对| sort -n -k 2
输出进行排序,如下所示:
56: 106496
1: 147456
55: 155648
解决方案 8:
使用 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>):
解决方案 9:
对于提到 Freebsd 的问题,很惊讶没有人写这个:
如果您想要 Linux 风格的 /proc/PROCESSID/status 输出,请执行以下操作:
mount -t linprocfs none /proc
cat /proc/PROCESSID/status
至少在 FreeBSD 7.0 中,默认没有进行挂载(7.0 是一个更老的版本,但是对于这种基本问题,答案隐藏在邮件列表中!)
解决方案 10:
检查一下,这是 gnome-system-monitor 的源代码,它认为一个进程“真正使用info->mem
”的内存是X Server Memory( info->memxserver
) 与 Writable Memory( ) 的总和( “可写内存” info->memwritable
),“可写内存”是/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);
}
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件