在c++动态内存分配的上下文中,我听说过几次“内存碎片”这个术语。我发现了一些关于如何处理内存碎片的问题,但找不到一个直接处理它本身的问题。所以:

什么是内存碎片? 如何判断内存碎片是否是我的应用程序的一个问题?什么样的程序最可能受到影响? 处理内存碎片的常用方法是什么?

另外:

我听说经常使用动态分配会增加内存碎片。这是真的吗?在c++环境中,我知道所有的标准容器(std::string, std::vector等)都使用动态内存分配。如果在整个程序中使用这些(特别是std::string),内存碎片更可能是一个问题吗? 在stl较多的应用程序中如何处理内存碎片?


当前回答

假设你有一个“大”(32字节)的空闲内存:

----------------------------------
|                                |
----------------------------------

现在,分配其中的一些(5个分配):

----------------------------------
|aaaabbccccccddeeee              |
----------------------------------

现在,释放前四个分配,但不释放第五个:

----------------------------------
|              eeee              |
----------------------------------

现在,尝试分配16个字节。哦,我不能,尽管有近两倍的免费。

在具有虚拟内存的系统上,碎片并不是您想象的那么大的问题,因为大的分配只需要在虚拟地址空间中连续,而不需要在物理地址空间中连续。所以在我的例子中,如果我有一个页面大小为2字节的虚拟内存,那么我可以毫无问题地分配16字节。物理内存看起来是这样的:

----------------------------------
|ffffffffffffffeeeeff            |
----------------------------------

而虚拟内存(要大得多)可能是这样的:

------------------------------------------------------...
|              eeeeffffffffffffffff                   
------------------------------------------------------...

内存碎片的典型症状是,您试图分配一个大块,但您不能,即使您看起来有足够的空闲内存。另一个可能的后果是进程无法将内存释放回操作系统(因为它从操作系统中分配给malloc等进行细分的每个大块中都有一些剩余的东西,即使每个块的大部分现在都没有使用)。

Tactics to prevent memory fragmentation in C++ work by allocating objects from different areas according to their size and/or their expected lifetime. So if you're going to create a lot of objects and destroy them all together later, allocate them from a memory pool. Any other allocations you do in between them won't be from the pool, hence won't be located in between them in memory, so memory will not be fragmented as a result. Or, if you're going to allocate a lot of objects of the same size then allocate them from the same pool. Then a stretch of free space in the pool can never be smaller than the size you're trying to allocate from that pool.

一般来说,您不需要太担心它,除非您的程序是长时间运行的,并且进行了大量的分配和释放。当您同时拥有短寿命和长寿命对象时,您的风险最大,但即使在这种情况下,malloc也会尽最大努力提供帮助。基本上,忽略它,直到您的程序出现分配失败或意外地导致系统内存不足(在测试中捕获它,这是首选!)。

标准库并不比其他任何分配内存的工具差,标准容器都有一个Alloc模板参数,如果绝对必要,您可以使用它来微调它们的分配策略。

其他回答

内存碎片是因为请求不同大小的内存块。考虑一个100字节的缓冲区。您请求两个字符,然后是一个整数。现在释放这两个字符,然后请求一个新的整数——但是这个整数不能容纳这两个字符的空间。该内存不能被重用,因为它不在一个足够大的连续块中,无法重新分配。除此之外,还为字符调用了大量分配器开销。

从本质上讲,在大多数系统上,内存只以一定大小的块形式存在。一旦你把这些块分开,它们不能重新连接,直到整个块被释放。这可能导致整个区块都在使用,而实际上只有一小部分区块在使用。

The primary way to reduce heap fragmentation is to make larger, less frequent allocations. In the extreme, you can use a managed heap that is capable of moving objects, at least, within your own code. This completely eliminates the problem - from a memory perspective, anyway. Obviously moving objects and such has a cost. In reality, you only really have a problem if you are allocating very small amounts off the heap often. Using contiguous containers (vector, string, etc) and allocating on the stack as much as humanly possible (always a good idea for performance) is the best way to reduce it. This also increases cache coherence, which makes your application run faster.

您应该记住的是,在一个32位x86桌面系统上,您有一个完整的2GB内存,它被分割成4KB的“页”(非常确定所有x86系统上的页大小是相同的)。您将不得不调用一些omgwtfbbq片段来解决问题。碎片确实是过去的一个问题,因为现代堆对于绝大多数应用程序来说都太大了,而且有一些流行的系统能够承受它,比如托管堆。

什么是内存碎片?

When your app uses dynamic memory, it allocates and frees chunks of memory. In the beginning, the whole memory space of your app is one contiguous block of free memory. However, when you allocate and free blocks of different size, the memory starts to get fragmented, i.e. instead of a big contiguous free block and a number of contiguous allocated blocks, there will be a allocated and free blocks mixed up. Since the free blocks have limited size, it is difficult to reuse them. E.g. you may have 1000 bytes of free memory, but can't allocate memory for a 100 byte block, because all the free blocks are at most 50 bytes long.

另一个不可避免但问题较少的碎片来源是,在大多数架构中,内存地址必须对齐到2,4,8等字节边界(即地址必须是2,4,8的倍数等)这意味着,即使你有一个包含3个char字段的结构,你的结构可能有12而不是3,因为每个字段都对齐到4字节边界。

如何判断内存碎片是否是我的应用程序的一个问题?什么样的程序最可能受到影响?

最明显的答案是内存不足异常。

显然,在c++应用程序中,没有一种好的便携式方法来检测内存碎片。更多细节请看这个答案。

处理内存碎片的常用方法是什么?

这在c++中很困难,因为你在指针中使用直接内存地址,你无法控制谁引用特定的内存地址。因此,重新安排已分配的内存块(Java垃圾收集器的方式)是不可取的。

自定义分配器可以通过在较大内存块中管理小对象的分配,并重用该块中的空闲插槽来提供帮助。

关于内存碎片的详细答案可以在这里找到。

http://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/

这是11年来我一直在softwareverify.com上回答人们关于内存碎片问题的答案的高潮

这是一个超级简化版的傻瓜。

当对象在内存中创建时,它们被添加到内存中已使用部分的末尾。

如果一个对象不在已使用内存部分的末尾被删除,这意味着这个对象位于其他两个对象之间,它将创建一个“洞”。

这就是所谓的碎片化。

当分配和释放许多大小不同的对象时,最可能发生内存碎片。假设你在内存中有如下布局:

obj1 (10kb) | obj2(20kb) | obj3(5kb) | unused space (100kb)

现在,当obj2被释放时,您有120kb的未使用内存,但是您不能分配120kb的完整块,因为内存是碎片化的。

避免这种影响的常用技术包括环形缓冲区和对象池。在STL的上下文中,像std::vector::reserve()这样的方法可以提供帮助。