如果/啥时候释放的堆内存被回收?

Posted

技术标签:

【中文标题】如果/啥时候释放的堆内存被回收?【英文标题】:If/When does the does deallocated heap memory get reclaimed?如果/什么时候释放的堆内存被回收? 【发布时间】:2012-08-24 02:58:29 【问题描述】:

我一直在嵌入式 Linux 系统上运行通宵内存测试。使用 vmstat 我观察到可用内存随着时间的推移稳步减少。根据 procfs 中的一些 smaps 分析,一个进程的堆以大致相同的速度增长。我怀疑存在内存泄漏,并在代码中发现了一些经常使用newdelete 的地方。但是,我没有看到没有匹配 delete 调用的 new 调用。

我再次运行内存测试,今天早上通过以下调用清除了内存缓存

echo 3 > /proc/sys/vm/drop_caches

vmstat 中列出的可用内存下降到接近开始测试时的值。

内核是否定期回收未使用的堆页面?如果是这样,除了上述之外,还有其他时间这样做吗?可能是当空闲内存低于某个阈值时?

【问题讨论】:

未使用的内存是浪费的内存,所以linux使用内存,直到其他人想使用它。 你说的是内核堆还是这个明显泄漏的进程使用的堆? @Tom:我说的是用户空间进程的堆 您能澄清一下您在说什么“空闲内存”吗?系统中有可用的物理 RAM?还是特定进程中未使用的虚拟地址空间? 【参考方案1】:

正如其他人所说,将内存返回给内核是进程的职责。

通常有两种分配内存的方法:如果您malloc()/new 内存块超过一定大小,则内存通过mmap() 从操作系统分配,并在空闲时立即返回。通过向上移动sbrk 边界来增加进程的数据区域来分配较小的块。只有在该段末尾有超过一定大小的块空闲时,才会释放此内存。

例如:(伪代码,我不太懂C++)

a = new char[1000];
b = new char[1000];

内存映射:

---------------+---+---+
end of program | a | b |
---------------+---+---+

如果你现在释放a,你中间有个洞。它没有被释放,因为它不能被释放。 如果你释放b,进程的内存可能会减少也可能不会减少;未使用的剩余部分返回系统。

一个简单的程序测试

#include <stdlib.h>

int main()

    char * a = malloc(100000);
    char * b = malloc(100000);
    char * c = malloc(100000);
    free(c);
    free(b);
    free(a);

导致strace 输出类似

brk(0)                                  = 0x804b000
brk(0x8084000)                          = 0x8084000
brk(0x80b5000)                          = 0x80b5000
brk(0x809c000)                          = 0x809c000
brk(0x8084000)                          = 0x8084000
brk(0x806c000)                          = 0x806c000

表示brk 的值首先增加(对于malloc())然后再次减少(对于free())。

【讨论】:

哪个运行时库可以做到这一点? 程序真的会通过sbrk 减小其大小吗?我一直认为,由于最后有一个完全空闲的块的机会是如此之低,以至于它永远不会费心减小大小。 @edA-qamort-ora-y 是的。请参阅我附加的示例。测试环境为Linux@i686。在 x86-64 上,观察到相同的行为。 这与我一直相信的完全背道而驰。如果在您的第​​一个示例中a 的大小超过两页,那么一旦它被释放,操作系统就可以回收那里的页面。这就是我们首先拥有页面的原因之一。仅仅因为虚拟地址是连续的,并不意味着物理部分必须是连续的。 不是物理部分,对。这对于在那里使用的操作系统是完全免费的。但要点是:一旦应用程序通过sbrk 分配内存并使用它们,操作系统必须将数据保存在那里,无论是在RAM 中还是在交换中。只有当程序再次减少sbrk 时,内容才可能被丢弃。所有这些仅适用于较小的malloc() 尺寸;更大的通过mmap() 提供服务。阈值是M_MMAP_THRESHOLDmallopts 之一。 M_TRIM_THRESHOLD 也很有趣。【参考方案2】:

内核将在需要时回收缓存的内存页面,即当系统内存不足时。进程堆(空闲存储)中的内存页是否返回到操作系统由进程的内存管理器决定,在这种情况下是 C++ 库中的new/delete 实现。这是一个完全自愿的操作,与内核无关。

drop_caches 成功的事实来看,您可以推断是内核缓存而不是进程的堆正在填满内存。使用free 命令找出实际有多少内存可供应用程序使用,尤其是。它报告的-/+ buffers/cache 行。

【讨论】:

【参考方案3】:

在程序中调用delete 会导致内存返回到作为程序运行时一部分的内存管理器。原则上,这可以写成将释放的内存返回给操作系统,但如果这样做我会感到惊讶。而是保留回收的内存以供后续调用new

请注意,这是您进程的虚拟内存;在程序执行期间的任何时候,有多少实际驻留在物理内存中取决于整体系统负载并由操作系统处理。

【讨论】:

【参考方案4】:

据我所知,用户对 malloc 和 free(或 new 和 delete)的调用永远不会将不再使用的页面返回给 O/S。相反,它们只记住已释放的内存,因此如果您执行的 malloc/new 的大小可以由先前释放的内存满足,那么它将使用该内存,而不是转到 O/S 并使用 sbrk 来获取更多内存。

因此这段代码:

for (;;)

    struct  char data[200 * 1024 * 1024]  HugeBuffer;
    HugeBuffer *buff = new HugeBuffer;
    delete buff;

将分配 200Mb 一次,然后永远稳定地使用该内存。它会在原始分配上去O/S一次,然后在用户空间循环摆弄。

【讨论】:

以上是关于如果/啥时候释放的堆内存被回收?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript基础知识六(内存释放作用域销毁)

垃圾分代回收机制

java中的堆和栈

java——GC回收机制

java中的垃圾回收机制是怎么回事?

java内存泄漏的定位与分析