C++ 为什么分配/取消分配一些小对象后内存不能重用?
在调查我们一个项目中的内存链接时,我遇到了一个奇怪的问题。不知何故,当父容器超出范围时,分配给对象的内存(shared_ptr to object的向量,见下文)没有完全回收,并且除了小对象之外无法使用 最简单的例子:当程序启动时,我可以毫无问题地分配1.5Gb的单个连续块。在我稍微使用内存之后(通过创建和销毁一些小对象),我就不能再进行大块分配了 测试程序:C++ 为什么分配/取消分配一些小对象后内存不能重用?,c++,vector,memory-leaks,stl,crt,C++,Vector,Memory Leaks,Stl,Crt,在调查我们一个项目中的内存链接时,我遇到了一个奇怪的问题。不知何故,当父容器超出范围时,分配给对象的内存(shared_ptr to object的向量,见下文)没有完全回收,并且除了小对象之外无法使用 最简单的例子:当程序启动时,我可以毫无问题地分配1.5Gb的单个连续块。在我稍微使用内存之后(通过创建和销毁一些小对象),我就不能再进行大块分配了 测试程序: #include <iostream> #include <memory> #include <vecto
#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();
}
#包括
#包括
#包括
使用名称空间std;
类大类
{
私人:
双a[10000];
};
void TestMemory(){
cout这不是内存泄漏。U使用的内存是由C\C++运行时分配的。运行时从操作系统中应用一次大容量内存,然后您调用的每个新内存都将从该大容量内存中分配。删除一个对象时,运行时不会立即将内存返回操作系统,它可能会保留该内存以提高性能。这里没有任何内容会影响您的性能表明存在真正的“泄漏”。您描述的内存模式并非意外。以下几点可能有助于理解。发生的情况高度依赖于操作系统
- 一个程序通常有一个可以扩展或收缩长度的堆。但是,它是一个连续的内存区域,因此改变大小只是改变堆的末端位置。这使得“返回”非常困难内存到操作系统中,因为即使在这个空间中有一个很小的对象也可以防止它的收缩。在Linux上,你可以查找函数“brk”(我知道你在Windows上,但我认为它也有类似的功能)
- 大型分配通常使用不同的策略完成。不将它们放入通用堆,而是创建一个额外的内存块。当它被删除时,该内存实际上可以“返回”到操作系统,因为它保证没有任何东西在使用它
- 大块未使用的内存不会消耗大量资源。如果您通常不再使用内存,它们可能会被分页到磁盘。不要因为某些API函数说您正在使用内存而认为您实际上正在消耗大量资源
- API并不总是报告您的想法。由于各种优化和策略,实际上可能无法确定在特定时刻系统上使用和/或可用的内存量。除非您了解操作系统的详细信息,否则您无法确定这些值的含义
前两点可以解释为什么一堆小数据块和一个大数据块会导致不同的内存模式。后两点说明了为什么这种检测泄漏的方法没有用。要检测真正的基于对象的“泄漏”,通常需要一个跟踪分配的专用分析工具
例如,在提供的代码中:
TestBigBlock分配和删除数组,假设这使用了一个特殊的内存块,所以内存返回给操作系统
TestMemory为所有小对象扩展堆,并且从不向操作系统返回任何堆。在这里,从应用程序的角度来看,堆是完全可用的,但从操作系统的角度来看,它是分配给应用程序的
TestBigBlock现在失败了,因为尽管它将使用一个特殊的内存块,但它与heap共享整个内存空间,并且在2完成后就没有足够的内存空间了
< C++ >程序中有“<强>绝对没有内存泄漏<强> >。真正的罪魁祸首是<强>内存碎片。< /强>
为了确保(关于内存泄漏点),我在Valgrind上运行了这个程序,它在报告中没有给出任何内存泄漏信息
//Valgrind Report
mantosh@mantosh4u:~/practice$ valgrind ./basic
==3227== HEAP SUMMARY:
==3227== in use at exit: 0 bytes in 0 blocks
==3227== total heap usage: 20,017 allocs, 20,017 frees, 4,021,989,744 bytes allocated
==3227==
==3227== All heap blocks were freed -- no leaks are possible
请查看我对您在原始问题中提出的疑问/疑问的答复
罪魁祸首似乎是在TestMemory()之后,应用程序的
虚拟内存保持在827125760(与I的次数无关
叫它)。
是的,真正的罪魁祸首是TestMemory()函数中隐藏的碎片
"
当空闲内存被分割成小块并被分配的内存所分散时。当某些存储分配算法无法有效地对程序使用的内存进行排序时,这是它们的一个弱点。其结果是,尽管空闲内存可用,但实际上它是不可用的,因为它被划分为太小的块以满足应用的需求。
例如,考虑程序分配3个连续内存块然后释放中间块的情况。内存分配器可以使用这个空闲的内存块进行将来分配。但是,如果要分配的内存比这个空闲块大,则不能使用这个块。
上面的段落很好地解释了内存碎片。一些分配模式(如频繁分配和交易位置)会导致内存碎片,但其最终影响(.e.内存分配1.5GB失败)由于不同的OS/堆管理器具有不同的策略和实现,因此在不同的系统上会有很大的差异。
例如,您的程序在我的机器(Linux)上运行得非常好,但是您遇到了内存分配失败
关于您对VM大小的观察,保持不变:任务管理器中显示的VM大小与内存分配调用不成正比。它主要取决于处于提交状态的字节数。当您分配一些动态内存时(使用new/malloc)您不需要在这些内存区域中写入/初始化任何内容,它不会进入提交状态,因此VM大小不会因此受到影响。VM大小取决于许多其他因素,并且有点复杂,因此我们不应该依赖于Complete