【问题标题】:"Private memory" not released after catching bad_alloc despite object being destructed尽管对象被破坏,但在捕获 bad_alloc 后未释放“私有内存”
【发布时间】:2016-12-06 09:39:23
【问题描述】:

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

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

Qt 是不是在偷偷摸摸?还是win32延迟发布?

【问题讨论】:

  • win32 从不“延迟”发布 - 如果您调用 VirtualFree(p, 0, MEM_RELEASE) - 函数返回时将释放内存(当然 p 是正确的) - 所以 100% VirtualFree 不是调用或使用错误的参数调用
  • 一个QListQLists 的什么?这才是最重要的。
  • 属于QVariant。它基本上是一个电子表格。它通常包含字符串或 URL 形式的数字。

标签: c++ qt winapi memory bad-alloc


【解决方案1】:

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

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

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

【讨论】:

  • "该对象是一个 QList 的 QList。"没有人进行任何手动 WINAPI 调用。
  • 然后,我建议查看 QList 的源代码,以了解分配失败的确切位置以及 Qt 是否正确处理此类异常。我看到很多程序员对内存不足异常采取了一种不关心的策略。不过不知道 Qt 中使用了哪种“策略”。
【解决方案2】:

Martin Drab 的回答让我走上了正确的道路。调查堆分配我发现这个old message 澄清了发生了什么:

这里的问题是超过 512k 的块是直接调用 VirtualAlloc,以及比这更小的所有东西都被分配出去 堆段。坏消息是这些细分市场永远不会 释放(全部或部分),以便您获取整个地址 带有小块的空间,您不能将它们用于其他堆或块 超过 512 K。

问题不是 Qt 相关,而是 Windows 相关;我终于可以用一个普通的std::vector 的字符数组来重现它。即使在相应的分配被显式释放后,默认的堆分配器也不会改变地址空间段。比例是该进程可能会再次询问类似大小的缓冲区,并且堆管理器将节省重用现有地址段的时间,而不是压缩旧地址段以创建新地址段。

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

这就是为什么内存被标记为“私有”的原因,即使在被释放之后,即使提交的内存非常低,对于平均大小的 malloc 显然也不能被同一进程使用。

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

但还有更糟糕的消息:显然没有办法强制压缩堆段。我尝试使用thisthis 但没有运气;没有与 POSIX fork() 完全等价的东西(参见 herehere)。唯一的解决方案是做一些更底层的事情,比如creating a private heap 并在小分配后销毁它(如上面引用的消息中所建议的)或实现自定义分配器(可能有一些商业解决方案)。对于现有的大型软件来说,两者都是不可行的,最简单的解决方案是关闭进程并重新启动它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-25
    • 2016-07-14
    • 2021-05-31
    • 1970-01-01
    • 2017-02-27
    • 2018-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多