私有字节、虚拟字节、工作集是什么?

574
我正在尝试使用 perfmon 工具来调试进程中的内存泄漏。
以下是 perfmon 对这些术语的解释:
工作集是进程工作集的当前大小(以字节为单位)。 工作集是最近由进程中的线程触摸的内存页集。 如果计算机中的可用内存高于阈值,则即使未使用,也会将页面保留在进程的工作集中。 当可用内存低于阈值时,将从工作集中修剪页面。 如果需要它们,则在离开主存储器之前将其软错误回到工作集中。
虚拟字节是进程正在使用的虚拟地址空间的当前大小(以字节为单位)。 虚拟地址空间的使用不一定意味着对磁盘或主内存页的相应使用。 虚拟空间是有限的,进程可以限制其加载库的能力。
私有字节是该进程分配但不能与其他进程共享的内存的当前大小(以字节为单位)。
以下是我的问题:
1.是否应该测量私有字节以确保进程是否存在任何泄漏,因为它不涉及任何共享库,而且任何泄漏(如果发生)都来自进程本身?
2.进程消耗的总内存是多少? 它是虚拟字节还是虚拟字节和工作集的总和?
3.私有字节、工作集和虚拟字节之间是否存在关系?
4.是否有其他工具可以更好地了解内存使用情况?

4
更好的工具将是valgrind/helgrind,但不幸的是,在Windows下无法使用:( - Kornel Kisielewicz
2
如果进程的私有字节不增长,则没有内存泄漏。如果它们增长了,可能是由于内存泄漏,也可能是由于内存碎片。我认为仅从私有字节的增长来看很难确定其确切含义。因此,您应该测量私有字节以确保进程是否存在任何泄漏。 - user184968
1
@SergeiKurenkov 我们可以肯定的是,这绝对不会是由于“内存碎片化”引起的。 - Jamie Hanrahan
4个回答

600
  • 私有字节(Private Bytes)指的是进程可执行文件所请求的内存总量,而不一定是实际使用的内存。它们通常不包括内存映射文件(例如共享 DLL),但可能包括由这些文件分配的内存。私有字节也不仅限于物理内存; 它们可以被换页到磁盘或放置在待机页列表中。
  • 工作集(Working Set)是进程使用的总物理内存(RAM)。与私有字节不同,它还包括内存映射文件和其他各种资源,因此比私有字节更不准确。工作集中的内存是“物理”的,因为可以在没有页面错误的情况下寻址。但是,待机页列表也仍然在物理内存中,但没有在工作集中报告,这就是为什么当您最小化应用程序时,“Mem Usage”会突然下降的原因。
  • 虚拟字节(Virtual Bytes)是整个进程占用的虚拟地址空间总量。与工作集类似,它包括内存映射文件(共享 DLL),但还包括待机列表中的数据以及已经换出并位于磁盘上某个页面文件中的数据。系统中每个进程使用的总虚拟字节在重载下会相加,可能会比计算机实际拥有的内存多得多。
  • 因此,这个问题的简短答案是:这些值都不是可靠的指示器,显示一个可执行文件实际使用的内存量,而且它们都不适用于调试内存泄漏。

  • Private Bytes是应用程序实际分配的内存,但包括页面文件使用;
  • Working Set是非分页的Private Bytes加上内存映射文件;
  • Virtual Bytes是Working Set加上分页Private Bytes和备用列表。
  • 这里还有另一个问题;正如共享库可以在应用程序模块中分配内存,导致在应用程序的Private Bytes中可能会报告错误的正值,您的应用程序也可能会分配内存到共享的模块中,导致错误的负值。这意味着您的应用程序实际上可能存在内存泄漏,而根本不会在Private Bytes中表现出来。这种情况可能性很小,但是确实存在。

    Private Bytes是您的可执行文件正在使用的内存量的合理近似值,并且可用于帮助缩小内存泄漏候选列表;如果您看到数字不断增长并不断地增长,则需要检查该进程是否存在泄漏。然而,这不能证明是否存在泄漏。

    在Windows中,检测/纠正内存泄漏最有效的工具之一实际上是Visual Studio (链接指向有关使用VS进行内存泄漏的页面,而不是产品页面)。Rational Purify是另一个选择。微软还有一个更一般的最佳实践文档。在这个以前的问题中列出了更多工具。

    希望这能澄清一些事情!追踪内存泄漏是调试中最难做的事情之一。祝好运。


    33
    很抱歉,您的回答并不完全正确。私有字节(Private Bytes)是指进程可执行文件所请求的内存(RAM)数量,而不仅限于物理内存。因此,通过监视私有字节,您肯定可以检查大多数内存泄漏情况。尝试使用 ::VisualAlloc 来提交一个大块内存(比如1.5G),您应该能够看到您的私有字节远远大于工作集(Working Set)。这证明了您的“工作集是私有字节加上内存映射文件”的说法是错误的。 - Jay Zhu
    5
    实际上,我认为正确的理解是,“工作集是内存中的私有字节加上内存映射文件”。而私有字节是可以被交换出去的 - 你可能会看到私有字节比你的机器物理内存还要大。 - Jay Zhu
    3
    @Aaronaught: 你关于“可靠指标”和“适合调试”的第一条陈述有些令人困惑。 私有字节是应用程序内存空间泄漏的可靠指标。 它可能是一个依赖的DLL和间接的,但它是应用程序内存空间中的泄漏。 请解释一下为什么私有字节不能用于调试? 应用程序进程的完整内存转储应该能够告诉我们是什么在消耗这些内存。 我不确定我理解为什么它不能用于调试。 你能帮忙解释一下吗? - G33kKahuna
    2
    运行完整的!objsize,这应该显示立即堆中的任何固定对象。您可以通过检查eeheap -gc来确认。这应该向您展示卡住的音量在哪里。通常,如果以上所有命令都没有可用的提示,则您的私有字节将被GC中未收集的对象消耗。现在转到gchandles或gcleaks之一。这些命令应该告诉您无法映射哪些类型/对象地址。指针仍然存在,但对象已经消失了。这是未释放事件处理程序的如此分类问题。 - G33kKahuna
    1
    如果你正在进行.NET开发,我过去使用过的一个非常好的工具是RedGate软件的ANTS Memory profiler,它可以帮助你找到内存泄漏的源头。它会准确地显示哪些对象被保留在内存中。虽然仍需要你知道哪些对象不应该在内存中,并采取措施确保它们不在内存中,但只要告诉你哪些对象被保留通常就足够解决问题了。 - Louis Duran
    显示剩余8条评论

    18
    自从一开始,perfmon计数器的定义就出了问题,而且由于某种原因似乎很难修复。
    在MSDN上有一个关于Windows内存管理的很好的概述视频:“Mysteries of Memory Management Revealed”,它涵盖了比跟踪内存泄漏所需的主题更多的内容(例如工作集管理),但在相关主题上提供了足够的细节。
    为了给你一个关于perfmon计数器描述问题的提示,这里有一段来自MSDN上的“Private Bytes Performance Counter -- Beware!”的内幕故事:
    问:什么时候私有字节不是私有字节? 答:当它不在内存中时。
    私有字节计数器报告进程的提交负载。也就是说,已经分配给交换文件的空间量,用于保存私有内存的内容,以防需要将其交换出去。注意:我避免使用“保留”一词,因为可能会与虚拟内存中处于保留状态的情况混淆,而后者并未被提交。

    来自MSDN的“ Performance Planning ”:

    3.3 私有字节

    3.3.1 描述

    私有内存被定义为为进程分配的无法被其他进程共享的内存。当多个这样的进程在一台机器上执行时,此内存比共享内存更昂贵。传统的非托管DLL中的私有内存通常由C++静态变量组成,并且占据DLL的总工作集约5%。


    1
    因为这些非常好的示例展示了它是如何出错的,所以点赞! - Bruno Brant
    1
    第一句话是错误的。分配“私有字节”不需要在交换文件(实际上称为页面文件)中分配任何内容。您甚至不必拥有页面文件即可分配“私有字节”。事实上,分配私有字节不会立即在任何地方使用任何空间,并且可能永远不会使用分配的空间那么多。 - Jamie Hanrahan
    1
    第二句话也没有好到哪去。DLL代码中使用的私有字节不一定大部分是静态分配在DLL内部的。DLL代码完全可以调用VirtualAlloc、HeapAlloc(CRTL中的malloc和new)等函数。它还试图将私有内存大小描述为工作集大小的百分比,这是无意义的。前者是虚拟大小(对于每次使用相同输入的代码,都将是相同的),而后者是物理大小(根据机器的内存富余或缺乏情况,每次运行可能会大不相同)。 - Jamie Hanrahan
    2
    “内存管理的奥秘揭示”链接失效了。链接到一个好的网站是一件好事,但至少引用结论通常是更好的主意,因为“互联网永远不会忘记”这句话并不成立。 - Tommylee2k
    @Tommylee2k 编辑后的链接现在可以使用了。 - phooBarred

    15

    你不应该尝试使用perfmon、任务管理器或任何类似的工具来确定内存泄漏。它们适用于识别趋势,但对于特定任务(例如内存泄漏检测),它们报告的绝对数字太模糊和聚合以至于无法有用。

    此前回答这个问题的人已经很好地解释了各种类型。

    你询问了一个工具推荐:

    我推荐 Memory Validator。能够监视进行数十亿次内存分配的应用程序。

    http://www.softwareverify.com/cpp/memory/index.html

    免责声明:我设计了Memory Validator。


    1
    我甚至无法运行一个简单的类文件(在Java中)?怎么回事? - jn1kk
    我怀疑Stephen和Devil可能有某种关联,甚至是克隆... :D ;) - Robert Koritnik
    @StephenKellett,有试用版吗? - Pacerier
    @Pacerier 如果你点击链接,在页面左侧的购买选项上方,会有x86和x64版本的试用版。 - Bradley A. Tetreault

    5

    9
    这是因为C标准库内存函数使用自定义或Win32堆,它是在低级别进程内存管理之上的一种内存管理机制。 - Kyberias
    @Kyberias,那么我们怎样才能把它降低呢? - Pacerier
    while(1) free(malloc(1000)); // 这会导致私有字节永远增加吗? - franckspike
    2
    @franckspike:不会,它会增加到一定程度(通常约为4 kB,但这可能会有所变化),然后停止,因为CRT将重新使用先前释放的内存而不是从操作系统请求新页面。 - Miral
    @Pacerier:你可以调用VirtualAlloc和VirtualFree。 - Jamie Hanrahan
    CRT和堆管理器是建立在VirtualAlloc之上的。使用VirtualAlloc和VirtualFree时,提交大小会增加和减少,但请注意MSDN中的这个说明:“除非/直到实际访问虚拟地址,否则不会分配实际物理页面。” - gast128

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接