3

所以我有一个本机 C++ 应用程序,它需要长时间跟踪很多事情。当任务管理器说进程达到 800 到 1200 MB 内存之间的某个位置时,它的内存不足,而限制应该是大约 2GB。

当我对我的进程运行 VMMap 时,我终于知道发生了什么,但这只是给了我更多的问题。我发现了什么:

  • 总大小(类型:总计,列:大小)比任务管理器/进程资源管理器报告的要大得多
  • 在我的程序内存不足之前,总大小似乎实际上是不能超过 2GB 的值
  • 内存使用差异几乎完全是由“私人数据”引起的——“大小”比“承诺”多得多。我见过大约 800MB 已提交的私有数据,但“大小”约为 1700MB。
  • “私有数据”的最大块主要由一对具有“读/写”保护并完全提交的小子块(通常在 4K 和 16K 之间)和一个较大的子块(介于90K 和 400K)具有“保留”保护且未提交。这似乎是对资源的巨大浪费。并且通常在末尾有一个大的(数兆字节)子块是“保留的”并且未提交。
  • 这对中的一小部分通常有我认识的字符串,而较大的部分根本没有字符串。

这些子块对的一个例子:(不是我的应用程序,但想法是一样的) http://www.flickr.com/photos/95123032@N00/5280550393

似乎当一个私有数据块被完全提交时,一个新块(通常与前一个最大块的大小相同或两倍)被分配。听起来很公平。但是,我看到了 3 个块,每个块都超过 100MB,提交的块不到 30MB。我的应用程序不应该以这种可能的方式运行(即用完 400MB,然后在几个小时内缩小 300MB)。

据我所知,“大小”是已分配的虚拟内存地址空间的实际数量。“已提交”是实际使用的“大小”数量(即通过调用 new/malloc)。如果确实如此,那为什么 Size 和 Commited 之间会有如此巨大的差异呢?为什么要分配数百兆字节的块?

有点奇怪的是,在 Windows 7 上运行时的行为完全不同。而在 2003 Server 上,应用程序使用私有数据,而在 Windows 7 上,应用程序使用堆。所以为什么?为什么 VMMap 在 2003 上主要显示私有数据使用情况,而在 7 上主要显示堆使用情况?有什么不同?一个区别是我不能使用 VMMap 中的“堆分配...”按钮来查看所有私有数据的分配位置。

我开始怀疑是否过度使用 std::string 导致了这个问题,因为我在成对中识别的字符串(如上所述)主要由存储在 std::string 中的字符串组成,这些字符串经常被创建和销毁(暗示很多内存分配/释放)。我将所有可能的转换为使用字符数组或使用内存池中的内存,但这似乎没有效果。我所有其他经常新建/删除的对象都已经拥有自己的内存池。

我还发现了低碎片堆,所以我尝试启用它,但它也没有任何区别。我认为这是因为 Windows 2003 实际上并没有正确使用堆。VMMap 显示启用了低碎片堆,但由于它实际上并没有被使用(即它使用的是私有数据),它实际上并没有什么不同。

实际上似乎正在发生的是,这些子块对正在分割大型私有数据块,这导致操作系统分配新块。最终,碎片变得如此糟糕,以至于即使有很多未提交的空间,但似乎没有一个可用,并且进程内存不足。

所以我的问题是:

  1. 为什么 Windows Server 2003 使用私有数据而不是堆?有关系吗?有没有办法让 Windows Server 2003 改用堆内存?如果是这样,那会改善我的情况吗?
  2. 有什么方法可以控制操作系统的内存分配器如何分配私有数据?
  3. 是否可以创建我自己的自定义堆并从中分配(不改变我的大部分代码库),这能改善我的情况吗?我知道可以制作自定义堆,但据我所知,您需要从自定义堆中显式分配,而不是仅仅调用 new 或仅正常使用 STL 容器。
  4. 我有什么遗漏或值得尝试的吗?
4

3 回答 3

3

私有数据只是对两个或多个进程之间未共享的所有内存的分类。堆、重定位的 dll 页面、进程中所有线程的堆栈、未共享的内存映射文件等都属于私有数据的范畴。

当条件之一为真时,来自进程的内存请求(通过 VirtualAlloc)将被操作系统失败,

  1. 连续的虚拟地址空间(不是内存)无法容纳请求的大小。
  2. 提交费用 - 所有进程和操作系统的总内存提交内存 - 已达到上限(即 RAM + 页面文件大小)

除此之外,堆分配可能由于其自身原因而失败,例如,在扩展期间,它们实际上会尝试获取比触发扩展的分配请求的大小更多的内存——如果失败,它们可能会失败——尽管实际请求的大小可能可通过 VirtualAlloc 获得。

容易积累记忆的东西很少,

  1. 有很多堆——它们会占用内存——因为它们保留了更多的储备。许多堆意味着很多保留空间可能未被使用。堆压缩可能会有所帮助。
  2. 像 vector 和 map 这样的 STL 容器在从中删除元素后可能不会缩小。压缩它们也可能有所帮助。
  3. 像 COM 这样的库会进行一些缓存并因此积累内存 - 可能有助于调查各个库以了解它们的内存占用习惯。
于 2013-03-29T19:27:17.963 回答
0

我遇到了同样的问题。

在 Windows 2003 中,我的应用程序在尝试使用 gcnew 分配 22MB 数组时导致 C++/CLI 模块内存不足异常。相同的过程在 Windows 7 中运行良好。

VMMap 显示 win2003 中的“私人数据”条目几乎是 2 GB。在我启用 /3GB 标志后,这个条目也增加到几乎 3GB。“堆”条目大约是 14 MB,而“托管堆”什么都不是!

在 Windows 7 中,“私有数据”仅为 62 MB,“堆”为 316MB,“托管堆”为 397MB。整个内存占用比win2003少很多。

于 2013-12-03T10:06:34.077 回答
0

当任务管理器说进程达到 800 到 1200 MB 的内存时,限制应该是 2GB 左右

可能您正在任务管理器中查看“工作集”,而 2GB 限制在虚拟内存上。任务管理器不显示保留的虚拟机数量;它将显示承诺的金额。

“已提交”是实际使用的“大小”数量(即通过调用 new/malloc)。

不,已提交意味着您实际触摸了该页面(即访问该地址并执行了加载或存储操作)。

1.为什么Windows Server 2003 使用私有数据而不是堆?

根据 Mark Russinovich 和 Aaron Margosis 的“Windows Sysinternals Administrator's Reference”:

私有数据内存是由 VirtualAlloc 分配的内存,堆管理器或 .Net 运行时不会进一步处理

因此,要么您的程序在两个操作系统上以不同方式管理其内存,要么 VMmap 无法检测到在 Windows Server 2003 上将此内存作为堆管理的方式。

4.我有什么遗漏或值得尝试的吗?

您可以在 32 位操作系统上运行 3GB 的限制,在 64 位操作系统上运行 32 位进程的 4GB 限制。谷歌搜索“/3G”和“/4G”。

Mark Russinovich、David Solomon 和 Alex Ionescu 所著的“Windows Internals 6th Edition”一书是有关此类信息的重要信息来源。

于 2013-08-20T12:19:45.043 回答