我有一个长期存在的应用程序,经常进行内存分配和释放。任何 malloc 实现都会将释放的内存返回给系统吗?

在这方面,以下行为是什么:

  • ptmalloc 1、2(glibc 默认)或 3
  • dlmalloc
  • tcmalloc(谷歌线程 malloc)
  • Solaris 10-11 默认 malloc 和 mtmalloc
  • FreeBSD 8 默认 malloc (jemalloc)
  • 囤积malloc?

更新

如果我有一个应用程序,其内存消耗在白天和夜间可能非常不同(例如),我可以强制任何 malloc 将释放的内存返回到系统吗?

如果没有这样的返回,释放的内存将被多次换出,但这样的内存只包含垃圾。

有帮助吗?

解决方案

以下分析仅适用于glibc(基于ptmalloc2算法)。有一些选项似乎有助于将释放的内存返回到系统:

  1. mallopt() (定义于 malloc.h)确实提供了一个选项来使用参数选项之一设置修剪阈值 M_TRIM_THRESHOLD, ,这表示数据段顶部允许的最小可用内存量(以字节为单位)。如果金额低于此阈值,glibc 会调用 brk() 将内存归还给内核。

    默认值为 M_TRIM_THRESHOLD 在Linux中设置为128K,设置较小的值可能会节省空间。

    通过在环境变量中设置修剪阈值可以实现相同的行为 MALLOC_TRIM_THRESHOLD_, ,绝对没有源变化。

    然而,初步测试程序运行使用 M_TRIM_THRESHOLD 已经表明,即使由 malloc 分配的内存确实返回到系统,实际内存块(arena)的剩余部分最初是通过 brk() 倾向于被保留。

  2. 可以通过调用来修剪内存区域并将任何未使用的内存返回给系统 malloc_trim(pad) (定义于 malloc.h)。该函数调整数据段的大小,至少留下 pad 如果可以释放的字节数少于一页,则失败。段大小始终是一页的倍数,在 i386 上为 4,096 字节。

    此修改行为的实现 free() 使用 malloc_trim 可以使用 malloc 挂钩功能来完成。这不需要对核心 glibc 库进行任何源代码更改。

  3. 使用 madvise() glibc 内部的系统调用自由实现。

其他提示

大多数实现不会费心识别那些(相对罕见)整个“块”(无论大小适合操作系统)已被释放并可以返回的情况,但当然也有例外。例如,我引用 维基百科页面, ,在 OpenBSD 中:

打电话给 free, ,使用MUNMAP从过程地址空间释放内存并将其取消启动。该系统旨在通过利用地址空间布局随机化和差距页面功能来提高安全性,并作为OpenBSD的一部分实现 mmap系统调用并检测无用的错误 - 释放后大型内存分配是完全未限制的,进一步使用会导致程序的分割故障和终止程序。

不过,大多数系统并不像 OpenBSD 那样注重安全。

知道这一点,当我编写一个长期运行的系统,该系统已知需要大量内存时,我总是尝试 fork 过程:然后,父级仅等待子级的结果[[通常在管道上]],子级进行计算(包括内存分配),返回结果[[在所述管道上]],然后终止。这样,我的长时间运行的进程就不会在内存需求偶尔出现“峰值”的较长时间内无用地占用内存。其他替代策略包括针对此类特殊要求切换到自定义内存分配器(C++ 使其相当容易,但底层具有虚拟机的语言(例如 Java 和 Python)通常不会这样做)。

我正在处理与OP相同的问题。到目前为止,tcmalloc 似乎是可行的。我找到了两个解决方案:

  1. 使用链接的 tcmalloc 编译您的程序,然后将其启动为:

    env TCMALLOC_RELEASE=100 ./my_pthread_soft
    

    文档 提到

    合理的比率在 [0,10] 范围内。

    但 10 对我来说似乎还不够(即我没有看到任何变化)。

  2. 在代码中找到释放所有已释放内存的有趣位置,然后添加以下代码:

    #include "google/malloc_extension_c.h" // C include
    #include "google/malloc_extension.h"   // C++ include
    
    /* ... */
    
    MallocExtension_ReleaseFreeMemory();
    

第二种解决方案对我来说非常有效;第一个会很棒,但不是很成功,例如找到正确的数字很复杂。

我在我的应用程序中遇到了类似的问题,经过一番调查后,我注意到由于某种原因,当分配的对象很小(在我的例子中小于 120 字节)时,glibc 不会将内存返回给系统。
看这段代码:

#include <list>
#include <malloc.h>

template<size_t s> class x{char x[s];};

int main(int argc,char** argv){
    typedef x<100> X;

    std::list<X> lx;
    for(size_t i = 0; i < 500000;++i){
        lx.push_back(X());
    }

    lx.clear();
    malloc_stats();

    return 0;
}

程序输出:

Arena 0:
system bytes     =   64069632
in use bytes     =          0
Total (incl. mmap):
system bytes     =   64069632
in use bytes     =          0
max mmap regions =          0
max mmap bytes   =          0

大约 64 MB 没有返回到系统。当我将 typedef 更改为:typedef x<110> X; 程序输出如下所示:

Arena 0:
system bytes     =     135168
in use bytes     =          0
Total (incl. mmap):
system bytes     =     135168
in use bytes     =          0
max mmap regions =          0
max mmap bytes   =          0

几乎所有内存都被释放了。我还注意到使用 malloc_trim(0) 在任何一种情况下都会向系统释放内存。
这是添加后的输出 malloc_trim 到上面的代码:

Arena 0:
system bytes     =       4096
in use bytes     =          0
Total (incl. mmap):
system bytes     =       4096
in use bytes     =          0
max mmap regions =          0
max mmap bytes   =          0

对于所有“正常”malloc,包括您提到的那些,内存都会被释放以供您的进程重用,但不会返回到整个系统。仅当进程最终终止时才会释放回整个系统。

在您列出的那些中,只有 Hoard 会将内存归还给系统......但如果它确实可以做到这一点,这在很大程度上取决于您的程序的分配行为。

简短的回答:要强制 malloc 子系统将内存返回给操作系统,请使用 malloc_trim()。否则,返回内存的行为取决于实现。

FreeBSD 12 的 malloc(3) 用途 杰马洛克 5.1,它将释放的内存(“脏页”)返回给操作系统 madvise(...MADV_FREE).

释放的内存仅在由控制的时间延迟后返回 opt.dirty_decay_msopt.muzzy_decay_ms;看到 手册页 和这个 关于实现基于衰减的未使用脏页清除的问题 更多细节。

早期版本的 FreeBSD 附带了旧版本的 jemalloc,它也返回释放的内存,但使用不同的算法来决定清除什么以及何时清除。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top