调用 free 或 delete 是不是曾经将内存释放回“系统”
Posted
技术标签:
【中文标题】调用 free 或 delete 是不是曾经将内存释放回“系统”【英文标题】:Does calling free or delete ever release memory back to the "system"调用 free 或 delete 是否曾经将内存释放回“系统” 【发布时间】:2010-11-28 03:13:32 【问题描述】:这是我的问题:调用 free 或 delete 是否会将内存释放回“系统”。我的意思是系统,它会减少进程的数据段吗?
让我们考虑一下 Linux 上的内存分配器,即 ptmalloc。
据我所知(如果我错了,请纠正我),ptmalloc 维护一个内存块的空闲列表,当内存分配请求到来时,它会尝试从这个空闲列表中分配一个内存块(我知道,分配器比这复杂得多,但我只是用简单的话来说)。但是,如果它失败了,它会使用 say sbrk 或 brk 系统调用从系统中获取内存。当内存被释放时,该块被放置在空闲列表中。
现在考虑这种情况,在峰值负载时,堆上分配了很多对象。现在,当负载减少时,对象就被释放了。所以我的问题是:一旦对象被释放,分配器会做一些计算来确定它是否应该只将这个对象保留在空闲列表中,或者根据空闲列表的当前大小,它可能决定将该内存还给系统即使用sbrk或brk减少进程的数据段?
glibc 的文档告诉我,如果分配请求远大于页面大小,它将使用 mmap 进行分配,并在释放后直接释放回系统。凉爽的。但是假设我从不要求分配大于 50 字节的大小,并且在系统的峰值负载时我要求很多这样的 50 字节对象。然后呢?
据我所知(请纠正我),在进程结束之前,使用 malloc 分配的内存永远不会释放回系统,即如果我释放它,分配器只会将它保留在空闲列表中。但是困扰我的问题是,如果我使用一个工具来查看我的进程的内存使用情况(我在 Linux 上使用 pmap,你们用什么?),它应该总是显示在峰值负载下使用的内存(因为内存永远不会归还给系统,除非使用 mmap 分配)?那就是进程使用的内存永远不会减少(堆栈内存除外)?是吗?
我知道我遗漏了一些东西,所以请对这一切有所了解。
专家,请澄清我对此的概念。我会很感激。我希望我能够解释我的问题。
【问题讨论】:
"它完全依赖于实现。"好的,很酷,假设 Linux 实现永远不会将内存返回给系统。那么我可以相信操作系统的页面替换以确保拥有大地址空间不会导致问题吗? 另外我想知道在 Linux 实现上会发生什么,即 ptmalloc 在上述场景中的行为是什么。 这篇文章的要点:我正在考虑为在我的应用程序中经常在堆上分配和释放的固定大小的对象编写我自己的内存池(在通用分配器即 malloc 之上) .因此,这将消除与从 malloc 获取对象相关的内存开销,并且分配和释放将是 O(1)(摊销)。所以我想知道池是否应该将内存归还给通用分配器,即 malloc 与否。我希望你们能明白这篇文章的要点。谢谢大家的回复。我很感谢大家的欢呼:) 【参考方案1】:malloc 的开销并不大,因此您不太可能实现任何运行时节省。然而,在 malloc 之上实现分配器是有充分理由的,那就是能够跟踪内存泄漏。例如,您可以在程序退出时释放所有由程序分配的内存,然后检查您的内存分配器是否调用平衡(即相同数量的分配/解除分配调用)。
对于您的具体实现,没有理由 free() 因为 malloc 不会释放到系统内存,因此它只会将内存释放回您自己的分配器。
使用自定义分配器的另一个原因是您可能分配了许多相同大小的对象(即您有一些要分配很多的数据结构)。您可能希望为此类对象维护一个单独的空闲列表,并仅从该特殊列表中释放/分配。这样做的好处是可以避免内存碎片。
【讨论】:
在具有虚拟内存的系统上内存碎片是否是一个问题? 是的,这样的碎片在虚拟地址空间中。【参考方案2】:它完全依赖于实现。在 Windows 上,如果相应的内存页只包含空闲块,则 VC++ 程序可以将内存返回给系统。
【讨论】:
【参考方案3】:没有。
由于多种原因,这实际上是一个糟糕的策略,所以它不会发生——除了——正如您所指出的,可以直接在页面中进行的大型分配可能存在例外。
它增加了internal fragmentation,因此实际上浪费内存。 (您只能将对齐的页面返回给操作系统,因此将对齐的页面从块中拉出通常会在块的任一侧创建两个保证为小块 - 无论如何都小于页面。如果这样发生了很多事情,你最终会得到相同数量的有用分配的内存加上大量无用的小块。)
kernel call 是必需的, 内核调用很慢,因此会减慢程序的速度。将块扔回堆中要快得多。
几乎每个程序都将收敛到稳态内存占用,或者在退出之前占用不断增加的内存。 (或者,直到接近退出。)因此,页面返回机制所需的所有额外处理都将完全浪费。
【讨论】:
【参考方案4】:我认为您拥有回答自己问题所需的所有信息。 pmap 显示进程当前正在使用的内存。因此,如果您在进程达到峰值内存之前调用 pmap,那么它不会显示峰值内存。如果您在进程退出之前调用 pmap,那么它将显示不使用 mmap 的进程的峰值内存。如果进程使用 mmap,那么如果您在使用最大内存的点调用 pmap,它将显示内存使用峰值,但该点可能不在进程结束时(它可能发生在任何地方)。
这仅适用于您当前的系统(即基于您免费提供的文档以及 mmap 和 malloc),但正如之前的海报所述,这些行为取决于实现。
【讨论】:
【参考方案5】:我相信 glibc 中的内存分配器可以将内存返回给系统,但是否会返回取决于您的内存分配模式。
假设你做了这样的事情:
void *pointers[10000];
for(i = 0; i < 10000; i++)
pointers[i] = malloc(1024);
for(i = 0; i < 9999; i++)
free(pointers[i]);
堆中唯一可以安全返回系统的部分是“荒野块”,它位于堆的末尾。这可以使用另一个 sbrk 系统调用返回给系统,当最后一个块的大小超过某个阈值时,glibc 内存分配器将执行此操作。
上述程序将进行 10000 次小分配,但只释放前 9999 个。最后一个应该(假设没有其他东西调用 malloc,这不太可能)位于堆的末尾。这将阻止分配器将任何内存返回给系统。
如果您要释放剩余的分配,glibc 的 malloc 实现应该能够将大部分分配的页面返回给系统。
如果您正在分配和释放小块内存,其中一些是长期存在的,您最终可能会遇到从系统分配的大块内存,但您只是在使用一小部分。
【讨论】:
【参考方案6】:这因实施而异。
把你的内存想象成一个巨大的长块,当你分配给它时,你会从你的内存中取出一点(下面标记为“1”):
111
如果我使用 malloc 分配更多内存,它会从系统中获得一些:
1112222
如果我现在释放“1”:
___2222
它不会返回给系统,因为它前面有两个(并且内存作为连续块给出)。但是,如果内存的末尾被释放,则该内存将返回给系统。如果我释放了“2”而不是“1”。我会得到:
111
“2”所在的位将返回给系统。 释放内存的主要好处是可以重新分配该位,而不是从系统中获取更多内存。例如:
33_2222
【讨论】:
现代系统中的内存分配比这复杂位。 我以前没见过这种东西。你有参考吗? 阅读系统调用“brk”。 @Artelius:从应用程序的角度来看,它有什么不同。 BSD 手册说“brk() 和 sbrk() 函数是虚拟内存管理出现之前遗留下来的历史奇闻。” 并且该页面还说“当分配大于 MMAP_THRESHOLD 字节的内存块时,glibc malloc() 实现使用 mmap(2) 将内存分配为私有匿名映射。”【参考方案7】:以下是永不将内存释放回系统的一些“优点”:
已经使用了大量内存,您很可能会再次使用,并且 当你释放内存时,操作系统必须做很多文书工作 当您再次需要它时,您的内存分配器必须重新初始化它刚刚收到的区域中的所有数据结构 释放的不需要的内存会被分页到磁盘上,但实际上并没有太大的区别 通常,即使您释放了 90% 的内存,碎片也意味着实际上可以释放的页面很少,因此寻找空页面所需的努力并没有得到充分利用【讨论】:
【参考方案8】:许多内存管理器可以执行 TRIM 操作,将完全未使用的内存块返回给操作系统。但是,正如这里的几篇文章所提到的,它完全取决于实现。
但是假设我从不要求分配大于 50 字节的大小,并且在系统的峰值负载时我要求很多这样的 50 字节对象。然后呢?
这取决于您的分配模式。你释放了ALL的小分配吗?如果是这样并且如果内存管理器处理了小块分配,那么这可能是可能的。但是,如果您分配了许多小项目,然后只释放除少数零散项目之外的所有项目,您可能会导致内存碎片化,并且无法修剪块,因为每个块将只有几个零散的分配。在这种情况下,您可能希望对临时分配和持久分配使用不同的分配方案,以便将临时分配返回给操作系统。
【讨论】:
以上是关于调用 free 或 delete 是不是曾经将内存释放回“系统”的主要内容,如果未能解决你的问题,请参考以下文章
如果已知 void* 分配大小,如何将确切的内存大小传递给 free()