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

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

另外:

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


当前回答

什么是内存碎片?

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垃圾收集器的方式)是不可取的。

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

其他回答

假设你有一个“大”(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模板参数,如果绝对必要,您可以使用它来微调它们的分配策略。

什么样的程序最可能受到影响?

关于与内存碎片相关的问题,一个很好的(=可怕的)例子是Stardock的电脑游戏“Elemental: War of Magic”的开发和发行。

这款游戏是基于32位/2GB内存构建的,所以必须在内存管理方面进行大量优化,以确保游戏能够在这2GB内存中运行。由于“优化”导致不断分配和取消分配,随着时间的推移,堆内存碎片发生,导致游戏每次都崩溃。

YouTube上有一个“战争故事”采访。

什么是内存碎片?

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垃圾收集器的方式)是不可取的。

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

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

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

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

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

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

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

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

这就是所谓的碎片化。