当/如果释放的堆内存何时被回收?

6
我一直在运行嵌入式Linux系统的内存过夜测试。使用vmstat,我观察到空闲内存随着时间的推移而稳步减少。根据procfs中的一些smaps分析,一个进程的堆在大致相同的速度增长。我怀疑存在内存泄漏,并在代码中找到了一些常规使用new和delete的地方。然而,我没有看到new调用没有匹配的delete调用。
我再次运行了内存测试,并在今天早上使用以下调用清除了内存缓存。
echo 3 > /proc/sys/vm/drop_caches

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

内核是否定期回收未使用的堆页面?如果是,除了上述时间之外,是否还有其他时间进行回收?当可用内存低于某个阈值时,可能会执行此操作。


4
未被使用的内存是浪费的内存,因此Linux会利用内存,直到有其他进程需要使用它。 - PlasmaHH
1
你是在说内核堆还是这个明显有泄漏的进程使用的堆? - Tom Tanner
@Tom:我在谈论用户空间进程的堆。 - waffleman
2
请问您所说的“空闲内存”是指什么?是系统中的空闲物理RAM,还是特定进程中未使用的虚拟地址空间? - AnT stands with Russia
4个回答

5

就像其他人所说的,归还内存给内核是进程的责任。

通常有两种分配内存的方式:如果你使用 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())。

那个使用哪个运行库? - Tom Tanner
一个程序通过sbrk会减小它的大小吗?我一直认为由于在结尾处有完全空闲块的机会非常低,所以它永远不会费心去减小大小。 - edA-qa mort-ora-y
@edA-qamort-ora-y 是的。请查看我附上的示例。测试环境是 Linux@i686。在 x86-64 上,观察到相同的行为。 - glglgl
不是物理部件,对吧。这完全由操作系统自由选择使用哪个。但重点是:一旦应用程序通过sbrk分配了内存并使用它们,操作系统必须保留放置在那里的数据,无论是在RAM中还是在交换空间中。只有当程序再次减少sbrk时,内容才可能被删除。所有这些仅适用于小的malloc()大小;更大的大小通过mmap()提供服务。阈值是M_MMAP_THRESHOLD之一malloptM_TRIM_THRESHOLD也很有趣。 - glglgl
@edA-qamort-ora-y 我从未遇到过一个运行时库,它在足够大的 malloc 中使用 mmap。 - Tom Tanner
显示剩余2条评论

1
内核将在需要时回收缓存的内存页面,即当系统否则会耗尽内存时。进程堆(自由存储区)中的内存页面是否返回给操作系统取决于进程的内存管理器,即C++库中的new/delete实现。这是一项完全自愿的操作,与内核无关。
从drop_caches起作用的事实可以推断出,填满内存的是内核缓存而不是进程的堆。使用free命令查找实际可供应用程序使用的内存量,特别是它报告的-/+缓冲/高速缓存行。

1

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

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


0
据我所知,用户调用malloc和free(或new和delete)时,从未将不再使用的页面返回给操作系统。相反,它们只是记住已释放的内存,因此如果您对可以满足以前释放的内存大小的malloc/new进行操作,则会使用该内存,而不是转向操作系统并使用sbrk获取更多内存。
因此,这段代码:
for (;;)
{
    struct { char data[200 * 1024 * 1024] } HugeBuffer;
    HugeBuffer *buff = new HugeBuffer;
    delete buff;
}

一开始分配200Mb的内存,然后永远稳定地使用这块内存。它在最初的分配时会与操作系统进行交互,之后就会在用户空间中循环进行操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接