"Private memory" 在捕获 bad_alloc 后未释放,尽管对象已被销毁

"Private memory" not released after catching bad_alloc despite object being destructed

一个对象试图分配比允许的虚拟地址更多的内存 space(在 win32 上为 2Gb)。 std::bad_alloc 被捕获,对象被释放。进程内存使用率下降,进程应该继续;但是,任何后续的内存分配都会因另一个 std::bad_alloc 而失败。使用 VMMap 检查内存使用情况表明,堆内存看似已释放,但实际上已标记为私有,没有空闲 space。唯一要做的似乎是退出并重新启动。我会理解碎片问题,但为什么进程在释放后无法收回内存?

该对象是 QListQList。该应用程序是多线程的。我可以做一个小的复制器,但我只能复制一次问题,而大多数时候复制可以再次使用释放的内存。

Qt 是在偷偷摸摸地做什么吗?或者是 win32 延迟发布?

据我了解您的问题,您正在从堆中分配大量内存,但有时会失败。将内存释放回进程堆并不一定意味着堆管理器实际上释放了仅包含堆空闲块的虚拟页面(由于性能原因)。因此,如果您尝试直接分配虚拟内存(VirtualAllocVirtualAllocEx),尝试将失败,因为几乎所有内存都被堆管理器消耗,而堆管理器没有机会知道您的直接分配尝试。

好吧,你可以用它做什么。您可以创建自己的堆 (HeapCreate) 并限制其最大大小。这可能非常棘手,因为您需要说服 Qt 使用这个堆。

分配大量内存时,我建议使用 VirtualAlloc 而不是堆函数。如果请求的大小 >= 512 KB,堆管理器实际上使用 VirtualAlloc 来满足您的请求。但是,我不知道当您释放该区域时它是否真的释放页面,或者它是否开始使用它来满足其他堆分配请求。

Martin Drab 的回答让我走上了正确的道路。调查堆分配我发现这个 old message 阐明了正在发生的事情:

The issue here is that the blocks over 512k are direct calls to VirtualAlloc, and everything else smaller than this are allocated out of the heap segments. The bad news is that the segments are never released (entirely or partially) so ones you take the entire address space with small blocks you cannot use them for other heaps or blocks over 512 K.

问题与Qt无关,而是Windows相关;我终于可以用一个简单的 std::vector 字符数组来重现它。默认堆分配器保留地址 space 段不变,即使在显式释放相应分配后也是如此。比率是进程可能会再次请求类似大小的缓冲区,堆管理器将节省时间重用现有地址段而不是压缩旧地址段以创建新地址段。

请注意,这与可用的物理或虚拟内存量无关。只有 地址 space 保持分段,即使这些分段是免费的。这在 32 位架构上是一个严重的问题,其中地址 space 只有 2Gb 大(可以是 3)。

这就是内存被标记为 "private" 的原因,即使在被释放之后,并且即使提交的内存非常低,对于平均大小的 mallocs 显然不能被同一进程使用。

要重现该问题,只需创建一个小于 512Kb 的巨大块向量(它们必须使用 new 或 malloc 分配)。在内存被填满然后释放后(无论是否达到限制并捕获异常或者内存只是被填满而没有错误),进程将无法分配大于 512Kb 的任何东西。内存是空闲的,它被分配给同一个进程("private")但是所有的桶都太小了。

但还有更坏的消息:显然没有办法强制压缩堆段。我尝试使用 this and this but had no luck; there is no exact equivalent of POSIX fork() (see here and here). The only solution is to do something more low level, like creating a private heap 并在小分配后销毁它(如上面引用的消息中所建议的)或实现自定义分配器(可能有一些商业解决方案)。对于现有的大型软件来说,两者都是不可行的,最简单的解决方案是关闭进程并重新启动它。