尽管对象被破坏,但在捕获bad_alloc后仍未释放“私有内存”

时间:2016-12-06 09:39:24

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

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

该对象是QList的{​​{1}}。该应用程序是多线程的。我可以制作一个小的复制器,但我只能重现一次这个问题,而大多数时候复制品可以再次使用被释放的存储器。

Qt做鬼鬼祟祟的事吗?或者也许是win32推迟发布?

2 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:1)

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

  

这里的问题是超过512k的块是直接调用   VirtualAlloc以及小于此的所有其他内容都已分配出去   堆段。坏消息是细分市场永远不会   释放(全部或部分)所以你取整个地址   小块空间你不能将它们用于其他堆或块   超过512 K.

问题与Qt无关,但与Windows有关;我终于可以用一个简单的std::vector char数组来重现它。即使明确释放了对应的分配,默认堆分配器也会使地址空间段保持不变。比例是该进程可能再次询问具有相似大小的缓冲区,并且堆管理器将节省重用现有地址段的时间,而不是压缩旧的地址段以创建新的地址段。

请注意,这与可用的物理内存和虚拟内存量无关。只有地址空间仍然是分段的,即使这些段是免费的。这是32位架构上的一个严重问题,其中地址空间仅为2Gb(可以是3)。

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

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

但是有更糟糕的消息:显然没有办法强制压缩堆段。我尝试了thisthis,但没有运气;没有完全相同的POSIX fork()(请参阅herehere)。唯一的解决方案是执行更低级别的操作,例如creating a private heap并在小分配之后销毁它(如上面引用的消息中所建议的)或实现自定义分配器(可能存在一些商业解决方案)。对于大型现有软件而言,这两者都是不可行的,其中最简单的解决方案是关闭进程并重新启动它。