在我们的一个项目中调查内存链接时,我遇到了一个奇怪的问题。不知何故,当父容器超出范围时,分配给对象的内存(对象的 shared_ptr vector ,见下文)没有完全回收,除了小对象外不能使用。
最小示例:当程序启动时,我可以毫无问题地分配一个连续的 1.5Gb block 。在我稍微使用内存之后(通过创建和销毁一些小对象),我不能再进行大块分配。
测试程序:
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class BigClass
{
private:
double a[10000];
};
void TestMemory() {
cout<< "Performing TestMemory"<<endl;
vector<shared_ptr<BigClass>> list;
for (int i = 0; i<10000; i++) {
shared_ptr<BigClass> p(new BigClass());
list.push_back(p);
};
};
void TestBigBlock() {
cout<< "Performing TestBigBlock"<<endl;
char* bigBlock = new char [1024*1024*1536];
delete[] bigBlock;
}
int main() {
TestBigBlock();
TestMemory();
TestBigBlock();
}
如果在循环中使用带有 new/delete 或 malloc/free 的普通指针而不是 shared_ptr,问题也会重复出现。
罪魁祸首似乎是在 TestMemory() 之后,应用程序的虚拟内存停留在 827125760(无论我调用它多少次)。因此,没有足够大的免费 VM 区域来容纳 1.5 GB。但我不确定为什么 - 因为我肯定会释放我使用的内存。 CRT 是否进行了某种“性能优化”以最大程度地减少操作系统调用?
环境是 Windows 7 x64 + VS2012 + 没有 LAA 的 32 位应用
最佳答案
很抱歉发布另一个答案,因为我无法发表评论;我相信其他很多人都非常接近答案:-)
无论如何,罪魁祸首很可能是地址空间碎片。我了解到您在 Windows 上使用 Visual C++。
C/C++ 运行时内存分配器(由 malloc 或 new 调用)使用 Windows 堆来分配内存。 Windows 堆管理器有一项优化,它会保留一定大小限制以下的 block ,以便在应用程序稍后请求类似大小的 block 时能够重用它们。对于较大的 block (我不记得确切的值,但我猜它大约是一兆字节),它将完全使用 VirtualAlloc。
其他具有许多小分配模式的长时间运行的 32 位应用程序也有此问题;让我意识到这个问题的是 MATLAB - 我使用“元胞数组”功能基本上分配了数百万个 300-400 字节的 block ,即使在释放它们之后也恰恰导致了地址空间碎片问题。
解决方法是使用 Windows 堆函数(HeapCreate() 等)创建私有(private)堆,通过它分配内存(根据需要将自定义 C++ 分配器传递给容器类),然后在需要时销毁该堆你想要回内存——这也有一个令人愉快的副作用,那就是与在一个循环中删除()无数个 block 相比,速度非常快..
回复。 “内存中剩余的内容”首先导致问题:“内存中”本身没有任何内容,更多的是释放 block 被标记为空闲但未合并的情况。堆管理器有一个地址空间的表/映射,它不允许您分配任何会迫使它将空闲空间合并到一个连续 block 中的东西(大概是一种性能启发式)。
关于c++ - 为什么在分配/取消分配一些小对象后内存不可重用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19653597/