虚拟大小导致程序内存耗尽

4

我正在处理的一个基于C++和Windows的服务器应用程序,在Virtual Size达到大约2GB时会出现内存不足的情况(32位应用程序,启用了大地址空间支持)。然而,我注意到Private Bytes显著较小。当前的统计数据是:

Virtual Size: 2.6GB Private Bytes: 1.6GB

这两个数字的差异为1GB。所以我的问题是:

  1. 这1GB的差异表示什么?
  2. 我的应用程序是因为Virtual Size还是Private Bytes而导致内存不足?

我还通过VMMap实用工具运行了我的应用程序,并注意到“Private Data”通常比已提交的大小高出一个数量级。换句话说,Private Data的总大小可能为200MB,但已提交的大小只有20MB。我不太确定私有数据是什么,但根据我目前的研究结果,它似乎只是堆的一部分。

编辑:

我使用Purify查找了内存泄漏,但并没有找到任何有用的信息。内存泄漏以无指针内存的形式出现似乎不是问题,但内存泄漏在于保留了太长时间的内存可能是一个问题,我还没有去研究。然而,关键是要理解为什么Virtual Size会导致内存不足问题。了解问题1对我来说是最重要的。


也许你有一个或多个需要修复的内存泄漏问题? - Paul R
已提交 = 应用程序实际使用的内存,私有 = 专门为应用程序保留的内存?虚拟大小 = 磁盘上分页的数据量,我猜是这样。我必须实际查看才能确定。 - JAB
@JAB 这是不正确的。我会发布一个解释答案。 - Polynomial
如果您正在尝试使用用户模式转储堆(UMDH)工具,则内存使用可能是由于GFlag收集堆栈跟踪;请参见Paul Arnold对https://dev59.com/qkrSa4cB1Zd3GeqPXpvU的回复。 - buzz3791
4个回答

6
这需要一些解释,请跟着我听。首先,这个话题是一堆相互矛盾的术语,所以请丢掉你认为“虚拟内存”与磁盘有关的概念。
物理内存是存储在物理设备上的内存。这通常指系统RAM,但也可以是磁盘缓存、NIC缓存、图形卡VRAM等。
虚拟内存是一组映射到用户模式(虚拟)地址范围中的物理地址范围,以便以安全和分隔的方式访问内存。
关于为什么要这样做的一个快速说明:如果我们直接给进程提供内存地址,我们只能(可行地)拥有单个内存存储。这很不方便,对性能也不利。当虚拟地址转换为物理地址超出系统内存(RAM)范围时,处理器会发出页面错误。这会向操作系统中的中断处理程序发出信号,然后可以将内存访问操作委派给另一个设备。非常有用!
在32位Windows系统上,进程可以在任何时候寻址的最大虚拟内存量为2GB。使用AWE可以将其增加到3GB,使用/4GT和AWE则可达到4GB。这并不意味着进程仅能分配2GB(或3GB / 4GB,取决于先前讨论的设置)的内存。只是意味着它不能同时访问超过这个数量。
例如,如果你打开一个1GB大小的内存映射文件,你的虚拟内存使用量将增加1GB。你没有触摸RAM,也没有触摸磁盘,但你已经为进程分配了一块虚拟地址空间。如果你想在同时拥有这个内存映射文件的情况下分配1.1GB的RAM,你就不行了。你必须先从进程中取消映射该文件。请记住,内存仍然可以保持分配和充满数据,但实际上并没有映射到你的进程中。如果你的机器有8GB RAM,你可以用6GB的数据填满它,并将其中的2GB映射到你的进程中。当你需要使用该内存的另一部分时,必须取消映射现有块并映射其他块。
所以,关于你看到的差异:
  1. 私有字节告诉您进程映射了多少字节的虚拟设备内存,不包括与其他进程共享的虚拟内存(例如映射的文件、全局堆等)。

  2. 工作集告诉您您正在积极使用多少字节的物理内存。这包括物理内存、设备缓冲区和映射的文件。这是一个相当奇怪的数字,因为它等同于已触摸的物理内存+映射的虚拟非系统内存。一般来说,您应该完全忽略此数字。对于调试内存泄漏来说几乎没有用处。

  3. 虚拟字节是您映射的虚拟内存总量。

区别在于您将共享的虚拟内存(例如一堆DLL文件或一块全局堆)映射到了您的进程中。差异表明这些共享映射的总大小约为1GB。

请记住,所有这些都与交换无关,交换是将系统内存页面转移到磁盘(所谓的“页面文件”),以增加快速系统资源(RAM)的可用性。该文件的命名在Windows的这个领域引起了无尽的混乱,当Microsoft最终决定将其称为“交换”而不是“虚拟内存”或“页面文件”时,我会感到欣慰。


哦,而且一定要读一下这个:http://blogs.technet.com/b/markrussinovich/archive/2008/11/17/3155406.aspx Mark Russinovich是这个低级内存方面的“大佬”。 - Polynomial
这可以使用PAE增加到3GB。实际上,PAE允许32位操作系统访问超过4GB的物理内存。AWE允许32位进程使用内存窗口访问超过2GB(或通过4GT访问3GB)用户内存。 4GT允许应用程序在虚拟地址空间的用户部分具有最多3GB的映射内存。 - Hristo Iliev
抱歉,我的术语混淆了。正在编辑以更正。 - Polynomial
我猜一个后续问题会是:为什么这些dll引入了如此高的共享内存使用? - void.pointer

2
  1. Virtual Size vs Private Bytes explained: 什么是私有字节、虚拟字节、工作集? (见下面的引文)
  2. 你的应用程序很可能会在2 GB处达到虚拟大小限制,特别是由于你自己已经看到了这样的行为。
  3. /LARGEADDRESSAWARE 只会在系统自身启用/3GB AKA 4GT时,扩展您的应用程序在Win32操作系统中的虚拟大小限制。

虚拟字节是整个进程所占据的虚拟地址空间总和。这就像工作集一样,因为它包括内存映射文件(共享DLL),但它还包括待机列表中的数据和已经被分页出去并坐落在磁盘上某个页面文件中的数据。每个进程在重负载下使用的虚拟字节数将累加到比机器实际具有的内存要多得多。

因此,关系如下:

  • 私有字节是你的应用程序实际分配的内存,但包括页面文件的使用;
  • 工作集是非分页的私有字节加上内存映射文件;
  • 虚拟字节是工作集加上分页的私有字节和待机列表。

我已经读了你提供的答案很多次,但是我仍然感到困惑。我想要知道为什么这些数字如此不同,但是除非我知道导致这种差异的确切原因,否则我甚至无法开始分辨出来。所以这是待机列表上的数据和/或存储在磁盘上的页面吗?如果是这样,为什么Windows不会清除未使用的页面(从而减少虚拟大小),而只是失败呢? - void.pointer
一个对于新手来说简单的事情:Private Bytes 计算了 newmalloc 分配的内存,只要它们成功,就有一个有效指针指向已分配的内存,因此它们会消耗地址空间。然而,你的所有代码也被映射到地址空间中,不仅仅是你的代码,还有所有的 API DLLs,它们都有自己的地址。它们都不属于 Private Bytes。所以,如果你打开 Process Explorer 或 Visual Studio 的模块窗格,你就会看到有多少个模块被加载到进程中,并且它们都计入了你所询问的差异。 - Roman R.

1
我在我的机器上遇到了类似的问题,其中C/C++/.NET win32应用程序耗尽了内存。它消耗了所有2GB的虚拟地址。看起来像是虚拟地址枯竭,因为Process Explorer仅显示应用程序占用了约900MB的内存。 VMMap显示1.6GB的私有数据和约700MB未提交的内存。

原因并不简单。应用程序验证器(C:\Windows\SysWOW64\appverif.exe)被配置为测试应用程序(基本测试标记为-vfbasics.dll)。将应用程序从应用程序验证器中删除后,应用程序正常工作。


我曾经遇到过同样的问题。为了继续使用应用程序验证器,我发现将appverif.exe从“完整页面堆模式”切换到普通的“正常页面堆模式”可以将受监视进程的内存使用量恢复到正常水平。在应用程序验证器>测试>基础>堆>右键菜单>属性中,通过切换“完整复选框”来在“完整模式”和“正常模式”之间进行切换。或者将Windows注册表_PageHeapFlags_字符串值设置为0x2以使用“正常模式”,0x3是“完整模式”。appverif.exe>帮助>堆停止详细信息>堆详细信息显示“正常”,直到“free()”强制崩溃。 - buzz3791

0
只是一条评论:启用大地址感知仅向操作系统发出信号,表明它可以安全地将从特定可执行文件创建的进程的虚拟地址空间以3:1的方式而不是通常的2:2方式进行划分。有各种各样的原因,说明应用程序应明确标识其支持3:1划分,但最明显的原因是,在2:2模式下,探测虚拟地址的MSB位可用于测试该地址是否属于虚拟地址空间的内核部分还是用户部分。在3:1中,这个测试不再有效,因为用户虚拟地址空间的1/3也是1。这要求调用此应用程序的驱动程序也应该知道可能存在3:1划分,并且应使用另一种方法来测试给定虚拟地址是否属于用户空间。
正如Roman R.所指出的那样,您仍然必须明确告诉内核启用对3:1 VA空间划分的支持。

请记住,在分配 /3GB 内存后,对于内存较小的大屏幕分辨率设备,它会导致剪贴板问题。截屏无法适应内核的剪贴板缓冲区,因此操作失败。这很奇怪! - Polynomial

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