为什么我无法释放那些大内存块

4

最近,我在调试程序时遇到了一个奇怪的错误。我们分配了一个大缓冲区。释放后,内存没有返回给操作系统。接下来的分配可能会因为内存不足而失败。然后我编写了下面的测试程序。我为指针c分配了内存,并没有使用free来模拟我的真实程序中由OpenMPI调用引起的小型内存泄漏。

在一个剩余3 GB内存的电脑(Mac)上,它将在第18行失败,因为内存不足。我知道指针c会导致内存碎片。但是对于这些像ab这样的大内存缓冲区,它们是通过mmap而不是brk进行分配的。它们应该能够直接被释放,对吧?我该怎么解决这个问题呢?

#include <stdlib.h>
#include <string.h>

int main(){

    float *a=malloc(1250000000);
    memset(a, (char)1, 1250000000);

    float *b=malloc(1250000000);
    memset(b, (char)1, 1250000000);

    float *c=malloc(10);
    memset(c, (char)1, 10);

    free(b);
    free(a);

    float *d=malloc(2000000000);  /* FAILS HERE */
    memset(d, (char)1, 2000000000);
}

2
要么不要进行如此大的内存分配,要么切换到64位目标。 - David Schwartz
这是Linux还是其他Unix变种?是哪一个版本? - jxh
一个32位寄存器可以存储2的32次方个不同的值...因此,一个具有32位内存地址的处理器可以直接访问4 GiB的字节可寻址内存。我认为这就是为什么David建议切换到64位目标,并拥有超过4GB的RAM来解决你在问题中提到的问题。 - francis
2
但是我所有的测试机器都是64位的。 - ustcyue
2
始终检查callocmalloc等的失败...并可能禁用内存过度承诺。 - Basile Starynkevitch
显示剩余6条评论
1个回答

2
你在评论中提到你正在观察你的程序在Mac下的行为。假设你指的是OS X,那么你的程序并没有按照你所期望的方式运作。分配器会对每个调用进行调用,而调用不会导致,因为分配器预计要重用你分配的内存。
可以通过使用来验证这一点:
$ dtruss -f -t mmap ./a.out 
    PID/THRD  SYSCALL(args)          = return
57843/0x16bf38:  mmap(0x10C119000, 0x2000, 0x5, 0x12, 0x3, 0x1000)       = 0x10C119000 0
57843/0x16bf38:  mmap(0x10C11B000, 0x1000, 0x3, 0x12, 0x3, 0x3000)       = 0x10C11B000 0
57843/0x16bf38:  mmap(0x10C11C000, 0x1FC0, 0x1, 0x12, 0x3, 0x4000)       = 0x10C11C000 0

$

注意到三个调用了 mmap,但是没有调用 brksbrk或者 munmap

$ dtruss -f -t brk ./a.out 
    PID/THRD  SYSCALL(args)          = return

$ dtruss -f -t sbrk ./a.out 
    PID/THRD  SYSCALL(args)          = return

$ dtruss -f -t munmap ./a.out 
    PID/THRD  SYSCALL(args)          = return

$

这种优化策略可能有效,但为什么在需要新的、不同的内存块时没有免费的选项呢? - edmz
哦,你的程序在我的系统上执行完成了,因为我的 Mac 有 16 GB 的内存。如果你的 Mac 有足够的交换空间,你的程序应该能够完成(尽管速度会慢一些)。 - jxh
@black:嗯...分配器不够智能? - jxh
但是我在我们的Linux服务器上观察到了完全相同的行为。你知道如何避免这种情况吗? - ustcyue
嗨jxh,这对我们来说很困难。因为这是一个庞大的程序,而且这个问题出现在我们解决一些特定问题时。我们无法承担改变整个结构的代价。 - ustcyue
显示剩余4条评论

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