这是我的问题:调用free或delete是否会将内存释放回“system”。我的意思是,系统是否会减少流程的数据段?
让我们考虑Linux上的内存分配器,即ptmalloc。
据我所知(如果我错了请纠正我),ptmalloc维护一个免费的内存块列表,当内存分配请求到来时,它会尝试从这个空闲列表中分配一个内存块(我知道,分配器比这复杂得多,但我只是简单地说它)但是,它失败了,它使用say sbrk或brk系统调用从系统获取内存。当一个内存被释放时,该块被放置在空闲列表中。
现在考虑这种情况,在峰值负载时,已经在堆上分配了很多对象。现在当负载减少时,对象是免费的。所以我的问题是:一旦对象被释放,分配器会做一些计算,以确定它是否应该将此对象保留在空闲列表中,或者根据空闲列表的当前大小,它可能决定将该内存返回给系统即使用sbrk或brk减少过程的数据段。
glibc的文档告诉我,如果分配请求比页面大小大得多,它将使用mmap分配,并且一旦免费就会直接释放回系统。凉。但是让我说我从来没有要求分配大于50字节的大小,并且我在系统的峰值负载上询问了很多这样的50字节对象。什么?
据我所知(请更正),分配有malloc的内存永远不会被释放回系统,直到进程结束,即如果我释放它,分配器将简单地将它保留在空闲列表中。但是,令我困扰的问题是,如果我使用工具来查看我的进程的内存使用情况(我在Linux上使用pmap,你们使用什么?),它应该总是显示在峰值负载时使用的内存(因为内存永远不会返回给系统,除非使用mmap分配)?那个进程使用的内存永远不会减少(堆栈内存除外)?是吗?
我知道我错过了什么,所以请详细说明这一切。
专家们,请清楚我的相关概念。我会很感激。我希望我能够解释我的问题。
答案 0 :(得分:12)
malloc没有太多开销,因此您不太可能节省任何运行时间。但是,有一个很好的理由在malloc之上实现一个分配器,那就是能够跟踪内存泄漏。例如,您可以在程序退出时释放程序分配的所有内存,然后检查内存分配器是否调用balance(即分配/取消分配的调用次数相同)。
对于您的具体实现,没有理由free(),因为malloc不会释放到系统内存,所以它只会将内存释放回您自己的分配器。
使用自定义分配器的另一个原因是您可能正在分配许多相同大小的对象(即您有一些分配很多的数据结构)。您可能希望为此类对象维护单独的空闲列表,并且仅从此特殊列表中释放/分配。这样做的好处是可以避免内存碎片。
答案 1 :(得分:5)
完全依赖于实现。在Windows上,如果相应的内存页面只包含freed块,VC ++程序可以将内存返回给系统。
答案 2 :(得分:4)
我认为您拥有回答自己问题所需的所有信息。 pmap显示当前正由进程使用的内存。因此,如果在进程达到峰值内存之前调用pmap,则不会显示峰值内存。如果您在进程退出之前调用pmap,那么它将显示不使用mmap的进程的峰值内存。如果进程使用mmap,那么如果你在使用最大内存的位置调用pmap,它将显示峰值内存使用量,但这一点可能不在进程的最后(它可能出现在任何地方)。
这仅适用于您当前的系统(即基于您免费提供的文档以及mmap和malloc),但正如之前的海报所述,这些行为依赖于文件。
答案 3 :(得分:4)
由于多种原因,这实际上是一个糟糕的策略,因此它不会发生 - 除非 - 您注意到,可以在页面中直接进行大型分配的例外。
它会增加internal fragmentation ,因此实际上可以浪费内存。 (您只能将对齐的页面返回到操作系统,因此从块中拉出对齐的页面通常会创建两个保证为小的块 - 无论如何 - 比块的任何一个块都小 - 块的任何一侧。如果这样发生了很多事情,你最终会得到相同数量的有用分配内存和许多无用的小块。)
需要kernel call,并且内核调用很慢,因此会降低程序的速度。将块重新放回堆中要快得多。
几乎每个程序都会收敛于稳态内存,或者增加占用空间直到退出。 (或者,直到接近退出。)因此,页面返回机制所需的所有额外处理将被完全浪费。
答案 4 :(得分:3)
从实现到实现,这有点不同。
将你的记忆想象成一个巨大的长块,当你分配它时,你会从记忆中消失一点(下面标记为'1'):
111
如果我使用malloc分配更多内存,它会从系统中获取一些内容:
1112222
如果我现在释放'1':
___2222
它不会返回到系统,因为两个在它前面(并且内存作为连续块给出)。但是,如果释放内存的末尾,则将该内存返回给系统。如果我释放'2'而不是'1'。我会得到:
111
将'2'的位返回给系统。 释放内存的主要好处是可以重新分配该位,而不是从系统获取更多内存。 e.g:
33_2222
答案 5 :(得分:3)
我相信glibc 中的内存分配器可以将内存返回给系统,但它是否会取决于你的内存分配模式。
让我们说你做这样的事情:
void *pointers[10000];
for(i = 0; i < 10000; i++)
pointers[i] = malloc(1024);
for(i = 0; i < 9999; i++)
free(pointers[i]);
可以安全地返回到系统的堆的唯一部分是“wilderness chunk”,它位于堆的末尾。这可以使用另一个sbrk系统调用返回给系统,当这个最后一个块的大小超过某个阈值时,glibc内存分配器就会这样做。
上述程序会产生10000个小分配,但只释放它们的前9999个。最后一个应该(假设没有其他任何调用malloc,这不太可能)坐在堆的末尾。这样可以防止分配器将任何内存返回给系统。
如果要释放剩余的分配,glibc的malloc实现应该能够返回分配回系统的大部分页面。
如果你正在分配和释放一小块内存,其中一些内存很长,你最终可能会遇到从系统分配大量内存的情况,但你只是在使用它的一小部分。
答案 6 :(得分:1)
以下是永远不会将内存释放回系统的一些“优点”:
答案 7 :(得分:1)
许多内存管理器可以执行TRIM操作,它们将完全未使用的内存块返回给操作系统。但是,正如这里提到的几个帖子一样,它完全依赖于实现。
但是让我说我从来没有要求分配大于50字节的大小,并且我在系统的峰值负载上问了很多这样的50字节对象。什么?
这取决于您的分配模式。你是否免费所有的小额分配?如果是这样,并且内存管理器已经处理了小块分配,那么这可能是可能的。但是,如果你分配了许多小项目,然后只释放除了一些分散的项目之外的所有项目,你可能会破坏内存并使其无法使用TRIM块,因为每个块只有几个分散的分配。在这种情况下,您可能希望为临时分配和持久分配使用不同的分配方案,以便将临时分配返回给操作系统。