我在Windows 7上使用C ++和MSVC 9.0,并且还能够在带有MSVC 9.0的Windows XP SP3上进行测试和重现。
如果我分配1 GB的0.5 MB大小的对象,当我删除它们时,一切正常并且按预期运行。但是,如果我在删除它时分配1 GB的0.25 MB大小的对象,则内存仍保留(Address Space Monitor中为黄色),从那时起,只能用于小于0.25 MB的分配。
这个简单的代码将允许您通过更改typedef的结构来测试这两种方案。在分配并删除了结构后,它将分配1 GB的1 MB字符缓冲区,以查看char缓冲区是否将使用结构曾占用的内存。
struct HalfMegStruct
{
HalfMegStruct():m_Next(0){}
/* return the number of objects needed to allocate one gig */
static int getIterations(){ return 2048; }
int m_Data[131071];
HalfMegStruct* m_Next;
};
struct QuarterMegStruct
{
QuarterMegStruct():m_Next(0){}
/* return the number of objects needed to allocate one gig */
static int getIterations(){ return 4096; }
int m_Data[65535];
QuarterMegStruct* m_Next;
};
// which struct to use
typedef QuarterMegStruct UseType;
int main()
{
UseType* first = new UseType;
UseType* current = first;
for ( int i = 0; i < UseType::getIterations(); ++i )
current = current->m_Next = new UseType;
while ( first->m_Next )
{
UseType* temp = first->m_Next;
delete first;
first = temp;
}
delete first;
for ( unsigned int i = 0; i < 1024; ++i )
// one meg buffer, i'm aware this is a leak but its for illustrative purposes.
new char[ 1048576 ];
return 0;
}
您可以在下方Address Space Monitor内查看我的结果。让我强调,这两个最终结果之间的唯一区别是结构的大小被分配到1 GB标记。
这对我来说似乎是一个非常严重的问题,而且许多人可能会遭受甚至不知道这一问题。
答案 0 :(得分:9)
我无法肯定地说明了这种情况,但这确实看起来像是内存碎片(以其多种形式之一)。在释放内存之后,分配器(malloc)可能会保留不同大小的存储桶以启用快速分配,而不是直接将其返回给操作系统,它会保留存储桶,以便以后可以处理相同大小的分配。同样的记忆。如果是这种情况,则内存可用于进一步分配相同大小。
这种优化通常会对 big 对象禁用,因为它需要保留内存,即使不使用它也是如此。如果阈值介于两种尺寸之间,则可以解释行为。
请注意,虽然您可能会觉得这很奇怪,但在大多数程序中(不是测试,而是现实生活),内存使用模式会重复出现:如果您要求100k块一次,那么通常情况下您将会再来一遍。保留内存保留可以提高性能,实际上减少碎片,这些碎片将来自同一个桶中的所有请求。
如果您想投入一些时间,可以通过分析行为来了解分配器的工作原理。编写一些测试,获取大小X,释放它,然后获取大小Y,然后显示内存使用情况。修复X的值并使用Y.如果两个大小的请求都是从相同的桶中授予的,则您将没有保留/未使用的内存(左侧的图像),而当从不同的桶授予大小时您会看到对右边图像的影响。
我通常不会编写Windows代码,我甚至没有Windows 7,所以我不能肯定地说这是这种情况,但看起来确实如此。
答案 1 :(得分:2)
我可以在Windows 7下使用g ++ 4.4.0确认相同的行为,因此它不在编译器中。事实上,当getIterations()
返回3590
或更多时,程序会失败 - 你得到相同的截止值吗?这看起来像是Windows系统内存分配中的一个错误。知识渊博的人谈论内存碎片是非常好的,但是这里的一切都被删除了,所以观察到的行为绝对不应该发生。
答案 2 :(得分:1)
使用您的代码我执行了您的测试并获得了相同的结果。我怀疑大卫罗德里格斯在这种情况下是正确的。
我进行了测试并得到了与你相同的结果。似乎可能会出现这种“桶”行为。
我也尝试了两种不同的测试。我没有使用1MB缓冲区分配1GB数据,而是使用与删除后首次分配内存相同的方式。第二次测试我分配了半兆缓冲区,然后分配了quater meg缓冲区,每个缓冲区最多加512MB。两个测试最终都有相同的内存结果,只分配了512个没有大块的保留内存。
正如大卫所提到的,大多数应用程序倾向于进行相同大小的分配。人们可以清楚地看到为什么这可能是一个问题。
对此的解决方案可能是,如果以这种方式分配许多较小的对象,最好分配一大块内存并自行管理。然后,当你完成释放大块时。
答案 3 :(得分:1)
我和一些当局就这个问题进行了交谈(格雷格,如果你在外面,请打个招呼; D)并且可以证实大卫所说的基本上是正确的。
随着堆在分配~0.25MB对象的第一遍中增长,堆正在保留并提交内存。由于堆在删除传递中收缩,它以某种速度退出,但不一定释放它在分配传递中保留的虚拟地址范围。在最后一次分配过程中,1MB分配由于它们的大小而绕过堆,因此开始与堆竞争VA。
注意堆保留 VA,而不是保持提交。如果您感到好奇,VirtualAlloc和VirtualFree可以帮助解释不同的问题。这个事实并没有解决您遇到的问题,即流程耗尽了virtual address space。
答案 4 :(得分:0)
这是低碎片堆的副作用。
http://msdn.microsoft.com/en-us/library/aa366750(v=vs.85).aspx
你应该尝试禁用它以查看是否有帮助。针对GetProcessHeap和CRT堆(以及您可能已创建的任何其他堆)运行。