为啥反复分配和释放内存会耗尽系统所有内存?

Posted

技术标签:

【中文标题】为啥反复分配和释放内存会耗尽系统所有内存?【英文标题】:Why can I use up all the system's memory by repeatedly allocating and deallocating memory?为什么反复分配和释放内存会耗尽系统所有内存? 【发布时间】:2016-01-25 09:51:30 【问题描述】:

在下面的简单示例 C++ 代码中,我使用 new 在堆上分配了大量内存 (~4GB),然后使用 delete 再次释放它。在顶部(或其他一些内存监视器)中,我可以看到在程序关闭之前,4GB 不会返回给操作系统。

我了解到(在我的相关 question 中还有其他人)这是正常的,因为未使用的堆以大块的形式返回给操作系统,不需要在删除后立即返回。

我希望一旦它请求更多内存(例如,当我启动示例程序的第二个实例时),已经释放的内存就会返回给操作系统。不幸的是,情况似乎并非如此:当我在第一个实例释放内存后运行我的示例的第二个实例时,两个实例的消耗都上升到 8GB。我可以对多个实例重复此操作,直到系统的所有物理内存都用完并开始交换并变得无响应。

运行 4 个示例程序实例(每个实例在删除数据后在 std::cin 中等待)时,top 的输出如下所示:

Mem:     16065M total,    15983M used,       82M free,        0M buffers
Swap:     2053M total,     1323M used,      730M free,      139M cached

这真的是想要的行为还是我错过了什么?

我可以在不同的 Linux 系统上观察到这一点,但不能在 Mac OS 10.11 上观察到。在 Mac OS 上,内存在删除后立即返回给操作系统,这是我实际预期的情况。

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <vector>

class MyObject

public:
    MyObject(int r=1000)
    : array(new float[r])
    
        for (int i = 0; i<r;i++)
        
            array[i] = random();
        
    

    ~MyObject()
    
        delete[] array;
    

public:
    float* array;
;


int main(int argc,char*argv[])

    char a;
    const int count=1000000;

    std::cout<<"Start after input"<<std::endl;
    std::cin >> a;
    // /proc/PROC_ID/status at this point:
    // VmSize:     11468 kB
    // VmLck:          0 kB
    // VmHWM:        960 kB
    // VmRSS:        960 kB
    // VmData:       144 kB
    
        std::vector<MyObject*> vec(count);
        for(int i=0; i<count; i++)
        
            vec[i] = new MyObject;
        

        std::cout<<"Release after input"<<std::endl;
        std::cin >> a;
        // VmSize:   3972420 kB
        // VmLck:          0 kB
        // VmHWM:    3962016 kB
        // VmRSS:    3962016 kB
        // VmData:   3961096 kB

        for (int i=0; i<count; i++)
        
            delete vec[i];
            vec[i]=NULL;
        
    

    std::cout<<"Shutdown after input"<<std::endl;
    std::cin >> a;
    // VmSize:   3964604 kB
    // VmLck:          0 kB
    // VmHWM:    3962016 kB
    // VmRSS:    3954212 kB
    // VmData:   3953280 kB


    return 0;

【问题讨论】:

“我可以看到,在程序关闭之前,4GB 不会返回给操作系统” - 这一直被问到。这就是大多数操作系统的工作方式。 Linux 对大分配有一个例外,当它们被释放时,它们可能会被释放给操作系统。内存可供您重复使用(通过mallocnew)。一般来说,操作系统会让释放的内存页面被换出,它们不会显着影响整个系统的物理内存分配,因此无需担心。 您的问题很难理解,因为您一直在无法说出您在说什么的上下文中使用“内存”一词。您在谈论物理内存 (RAM) ?或者您是在谈论虚拟内存(地址空间)?还是后备存储?还是什么?很难判断您的问题是否只是基于对不同类型内存的混淆。 简短回答:如果您谈论的是物理内存,除非它被锁定,否则它总是已经发布到操作系统。操作系统使用物理内存,但它认为最好,除非它被锁定。如果您在谈论虚拟内存(地址空间),谁在乎呢?这不是稀缺资源。只要它可以重新用于后续分配,删除分配绝对没有任何好处。这是纯粹的成本,因为这意味着以后分配的工作量更多。 @TonyD 我希望我不必担心,但我可以一个接一个地运行该程序的 4 个实例(这样实际上只有一个实例实际上需要 4GB) ) 并且我的系统变得无响应。 如果有帮助,我非常乐意进一步编辑和澄清我的问题。但本质上,标题说明了一切:为什么我可以通过正确(据我所知)分配和释放内存来使系统达到极限。 【参考方案1】:

您平台的内存分配器无法按照您希望的方式处理这种特殊情况。最有可能的是,它只向操作系统返回足够大的分配,而这些分配太小。如果您有不寻常的要求,您应该选择一个已知满足这些要求的分配器。同时释放大量非常小的分配是一种不寻常的应用程序。

它可能就像一个包装分配器一样简单,它分配比请求大得多的块并将它们拆分。这应该使块足够大,以便您的平台分配器将它们一对一映射到操作系统请求的地址空间。

【讨论】:

那么通过“选择一个分配器”,你有什么建议(除了自己写)吗?我也很困惑,因为 glibc 的malloc() 似乎有一个 128k 的 M_TRIM_THRESHOLD,它应该被 OP 的用例触发一百万次,这是正确的。我只能得出结论,g++ 附带的标准分配器不使用 malloc 作为后端。否则,我会建议摆弄M_TRIM_THRESHOLD 和可能的M_MMAP_THRESHOLD,如果设置为较小的值,这将使操作系统可以使用任何已释放的内存。但它可能对 C++ 运行时没有影响。 @PeterA.Schneider M_TRIM_THRESHOLD 很少有用,尤其是在往往涉及大量小型临时分配的 C++ 程序中——您只需要一个字符串或在大量分配后分配的东西确保后者在前者被释放之前不会被释放。 M_MMAP_THRESHOLD 是具有一定承诺的参数:您需要获得一个内存池/分配器(boost 有一个可能适合的)并使用它从大分配中分出一小部分。不要在程序释放大部分内存后混入任何需要生存的对象。

以上是关于为啥反复分配和释放内存会耗尽系统所有内存?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 free() 不释放所有分配的内存位置? [复制]

析构函数为啥能释放对象内存?

Java内存管理——垃圾收集

linux采用啥方法实现内存的分配和释放

C语言free释放内存后为啥指针里的值不变?竟然还可以输出

Java内存回收机制