什么是内存碎片?

我曾经听说过在C ++dynamic内存分配的环境中使用了几次“内存碎片”。 我发现了一些关于如何处理内存碎片的问题,但是找不到直接处理内存的问题。 所以:

  • 什么是内存碎片?
  • 如何判断内存碎片对我的应用程序是否有问题? 什么样的节目最有可能遭受?
  • 处理内存碎片的常见方法是什么?

也:

  • 我听说使用dynamic分配很多可以增加内存碎片。 这是真的? 在C ++的上下文中,我理解所有的标准容器(std :: string,std :: vector等)都使用dynamic内存分配。 如果在整个程序中使用这些(特别是std :: string),内存碎片更可能是一个问题?
  • 内存碎片怎么处理在一个STL沉重的应用程序?

想象一下,你有一个“大”(32字节)的空闲内存:

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

现在,分配一些(5分配):

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

现在,免费的前四个分配,但不是第五个:

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

现在,尝试分配16个字节。 哎呀,我不能,即使有近一倍的免费。

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

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

而虚拟内存(更大)可能是这样的:

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

内存碎片的典型症状是,尽pipe您似乎有足够的内存空间,但您仍然尝试分配一个大块,并且不能。 另一个可能的后果是进程无法将内存释放回操作系统(因为从OS分配的所有块中仍然有一些对象正在使用,即使这些块现在大部分都未被使用)。

防止C ++中的内存碎片的策略是通过根据其大小和/或预期的生命周期来分配来自不同区域的对象来工作。 所以,如果你打算创build大量的对象并在以后将它们全部销毁,请从内存池中分配它们。 你在它们之间做的任何其他的分配将不会从池中,因此不会被放置在它们之间的内存中,因此内存不会被分割。

一般来说,你不需要担心,除非你的程序长时间运行,并且执行了大量的分配和释放。 这是当你有短寿命和长寿命的物体的混合物,你是最危险的,但即使如此malloc将尽最大的帮助。 基本上,忽略它,直到你的程序有分配失败或意外导致系统内存不足(在testing中捕获这个,首选!)。

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

什么是内存碎片?

内存碎片是当你的大部分内存被分配到大量的不连续的块或块中时 – 留下很大比例的总内存未分配,但是对于大多数典型场景是不可用的。 这会导致内存不足或分配错误(即malloc返回null)。

想想这个最简单的方法是想象你有一个很大的空墙,你需要把不同大小的图片放在上面。 每张照片都占用一定的尺寸,显然不能将其分割成更小的部分以使其合适。 你需要在墙上留下一个空白的地方,图片的大小,否则你就无法忍受。 现在,如果你开始在墙上挂图片,而且你不小心如何安排图片,那么很快你会看到一幅部分被图片覆盖的墙,即使你有空的地方,大多数新图片都不适合因为他们比可用的点大。 你仍然可以挂很小的照片,但大多数不适合。 所以你必须重新安排(紧凑)已经在墙上的东西,以腾出更多的空间。

现在,想象一下,墙是你的(堆)内存,图片是对象..这是内存碎片..

如何判断内存碎片对我的应用程序是否有问题? 什么样的节目最有可能遭受?

一个告诉你可能是处理内存碎片的迹象是,如果你得到很多分配错误,特别是当已经使用内存的百分比很高的时候 – 但是你还没有用完所有的内存 – 所以在技术上你应该有足够的空间为您要分配的对象。

当内存严重分散时,内存分配可能需要更长的时间,因为内存分配器必须做更多的工作来为新对象find合适的空间。 如果反过来你有很多的内存分配(你可能会做内存碎片),分配时间甚至可能导致明显的延迟。

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

使用一个好的algorithm来分配内存。 为许多小对象分配内存,而不是为这些较小对象的连续数组分配内存。 有时在分配内存时有点浪费,可能会影响性能,并可能为您节省处理内存碎片的麻烦。

内存碎片与磁盘碎片的概念是一样的:它指的是空间被浪费,因为所使用的区域没有被紧密地包装在一起。

假设一个简单的玩具例子,你有十个字节的内存:

  | | | | | | | | | | | 0 1 2 3 4 5 6 7 8 9 

现在让我们分配三个三字节块,名称A,B和C:

  | A | A | A | B | B | B | C | C | C | | 0 1 2 3 4 5 6 7 8 9 

现在释放块B:

  | A | A | A | | | | C | C | C | | 0 1 2 3 4 5 6 7 8 9 

现在如果我们尝试分配一个四字节块D,会发生什么? 那么,我们有四个字节的内存空间,但是我们没有四个连续的内存空间,所以我们不能分配D! 这是内存使用效率低下,因为我们应该已经能够存储D,但是我们无法做到。 而且我们不能移动C来腾出空间,因为很可能我们程序中的某些variables指向C,我们不能自动查找和更改所有这些值。

你怎么知道这是一个问题? 那么最大的问题就是你的程序的虚拟内存大小要比你实际使用的内存大得多。 在一个现实世界的例子中,你将会有十多个字节的内存,所以D会被分配开始一个字节9,而字节3-5将保持不被使用,除非你稍后分配了三个字节长或更小的东西。

在这个例子中,3个字节并不是一个很大的浪费,但是考虑一个更为病态的情况,例如,两个字节的两个分配在内存中分开十兆字节,并且需要分配一个大小为10兆字节的块+ 1个字节。 你必须去问操作系统超过10兆的虚拟内存来做到这一点,即使你只有一个字节害羞有足够的空间已经。

你如何预防呢? 当你经常创造和摧毁小物体时,往往会产生最坏的情况,因为这往往会产生“瑞士奶酪”的效果,许多小物体被许多小洞分隔开来,使得在这些小洞中不可能分配更大的物体。 当你知道你将要这样做的时候,一个有效的策略是预先分配一大块内存作为你的小对象的池,然后手动pipe理该块内的小对象的创build,而不是让默认分配器处理它。

一般来说,你做的分配越less,内存分解的可能性就越小。 然而,STL相当有效地处理这个问题。 如果你有一个正在使用当前分配的整个string,并且你添加了一个字符,它不会简单地重新分配给它的当前长度加上一个,它会增加一倍 。 这是“频繁小额分配”战略的一个变种。 该string抓取大量内存,以便可以有效地处理重复的小尺寸增量,而无需重复进行小的重新分配。 所有的STL容器实际上都是这样做的,所以一般情况下你不用担心自动重新分配STL容器造成的碎片。

虽然STL容器当然不会彼此之间集中内存,但是如果要创build许多小容器(而不是less数容器经常resize),那么您可能不得不关心如何防止碎片对于任何经常创build的小对象,STL与否。

  • 什么是内存碎片?

内存碎片是内存变得不可用的问题,尽pipe它在理论上是可用的。 有两种碎片: 内部碎片是分配但不能使用的内存(例如,当内存分配在8字节块中,但是当程序只需要4个字节时,程序重复执行单个通道)。 外部碎片是空闲内存被分割成许多小块的问题,所以尽pipe有足够的整体空闲内存,但是大的分配请求不能被满足。

  • 如何判断内存碎片对我的应用程序是否有问题? 什么样的节目最有可能遭受?

内存碎片是一个问题,如果您的程序使用更多的系统内存比它的实际paylod数据将需要(你已排除内存泄漏)。

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

使用一个好的内存分配器。 IIRC,那些使用“最适合”战略的企业,如果稍微慢一点,在避免分裂方面通常要优越得多。 但是,也显示出对于任何分配策略,都存在病态最差的情况。 幸运的是,大多数应用程序的典型分配模式实际上对分配器来说是相对良性的。 如果你对这些细节感兴趣的话,那里有大量的论文。

  • Paul R. Wilson,Mark S. Johnstone,Michael Neely和David Boles。 dynamic存储分配:调查与批判性回顾。 在“1995年国际记忆力pipe理研讨会论文集”中,Springer Verlag LNCS,1995年
  • Mark S.Johnstone,Paul R. Wilson。 内存碎片问题:解决? ACM SIG-PLAN通告,第34卷第3号,第26-36页,1999年
  • MR Garey,RL Graham和JD Ullman。 内存分配algorithm的最坏情况分析 在第四届ACM计算理论研讨会上,1972年

更新:
谷歌TCMalloc:线程cachingMalloc
已经发现,在长时间运行过程中处理碎片是非常好的


我一直在开发一个在HP-UX 11.23 / 11.31 ia64上遇到内存碎片问题的服务器应用程序。

看起来像这样 有一个过程使内存分配和释放,并跑了几天。 即使没有内存泄漏,进程的内存消耗也在不断增加。

关于我的经验。 在HP-UX上,使用HP-UX gdb很容易find内存碎片。 你设置了一个断点,当你点击它时,你运行这个命令: info heap并查看进程的所有内存分配和堆的总大小。 然后你继续你的程序,然后一段时间后你再次击中断点。 你再次做info heap 。 如果堆的总大小更大,但单独分配的数量和大小相同,那么很可能是内存分配问题。 如有必要,请在前几次检查。

这是我改善形势的方法。 在使用HP-UX gdb进行了一些分析后,我发现内存问题是由于我使用std::vector来存储数据库中某些types的信息而造成的。 std::vector要求其数据必须保存在一个块中。 我有一些基于std::vector容器。 这些容器经常重新创build。 通常情况下,新的logging被添加到数据库中,然后容器被重新创build。 而且由于重新创build的容器比较大,所以它们不适合可用内存空间,运行时需要从操作系统中获得更大的空间。 结果,即使没有内存泄漏,进程的内存消耗也在增长。 当我更换容器时,我改善了情况。 而不是std::vector我开始使用std::deque ,它有一个不同的方式为数据分配内存。

我知道在HP-UX上避免内存碎片的一种方法是使用Small Block Allocator或使用MallocNextGen。 在RedHat Linux上,默认分配器似乎可以很好地处理很多小块。 在Windows上有Low-fragmentation Heap ,它解决了大量小分配的问题。

我的理解是,在一个STL沉重的应用程序中,您首先要识别问题。 内存分配器(如在libc中)实际上处理了很多小分配的问题,这是std::string典型例子(例如,在我的服务器应用程序中有很多STLstring,但从运行info heap看到它们不是造成任何问题)。 我的印象是,你需要避免频繁的大量分配。 不幸的是,有些情况下,你无法避免它们,必须改变你的代码。 正如我在我的情况说,我改善了情况时,切换到std::deque 。 如果你确定你的记忆碎片,可以更准确地谈论它。

内存碎片最有可能发生在您分配和取消分配多个不同大小的对象时。 假设你在内存中有以下布局:

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

现在,当obj2被释放时,你有120kb的未使用的内存,但是你不能分配一个120kb的完整块,因为内存被分割了。

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

关于内存碎片的非常详细的回答可以在这里find。

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

这是11年的内存碎片答案的高潮,我已经提供给人们,问我关于在softwareverify.com的内存碎片的问题

什么是内存碎片?

当您的应用程序使用dynamic内存时,它将分配并释放内存块。 一开始,您的应用程序的整个内存空间是一个连续的空闲内存块。 然而,当你分配和释放不同大小的块时,内存开始被分割 ,也就是说,而不是一个大的连续的空闲块和一些连续的分配块,将有一个分配的和空闲的块混在一起。 由于空闲块的大小有限,难以重用。 例如,可能有1000字节的空闲内存,但不能为100字节块分配内存,因为所有空闲块的长度至多为50个字节。

另一个不可避免的问题是碎片来源,在大多数体系结构中,内存地址必须 2,4,8等字节边界alignment (即地址必须是2,4,8等的倍数)。这意味着即使你有例如一个包含3个char字段的结构体,由于每个字段都alignment到一个4字节的边界,所以你的结构体的大小可能是12而不是3。

如何判断内存碎片对我的应用程序是否有问题? 什么样的节目最有可能遭受?

显而易见的答案是你得到了一个内存不足的例外。

显然没有好的便携式的方式来检测C ++应用程序中的内存碎片。 看到这个答案的更多细节。

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

在C ++中很难,因为你在指针中使用直接内存地址,而且你不能控制谁引用了特定的内存地址。 因此,重新安排分配的内存块(Java垃圾回收器的方式)不是一个选项。

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

这是傻瓜的超级简化版本。

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

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

这就是所谓的碎片。

当你想在堆上添加一个项目时,发生的事情是计算机必须search空间以适应该项目。 这就是为什么dynamic分配时没有对内存池或池分配器进行分配可以“减慢”的东西。 对于繁重的STL应用程序,如果您正在执行multithreading,则有Hoard分配器或TBB Intel版本。

现在,当内存碎片时,可能会发生两件事情:

  1. 必须有更多的search来find一个很好的空间来粘“大”的物体。 也就是说,许多散落的小物件在一定条件下可能难以发现一个很好的连续的记忆块(这是极端的)。
  2. 内存不是一些容易阅读的实体。 处理器仅限于它们可以容纳多less以及在哪里。 他们通过交换页面来做到这一点,如果他们需要的项目是一个地方,但目前的地址是另一个地方。 如果您经常不得不交换页面,则处理速度可能会变慢(再次,这会影响性能的极端情况)。请参阅虚拟内存上的这篇文章。

发生内存碎片是因为请求了不同大小的内存块。 考虑一个100字节的缓冲区。 你请求两个字符,然后是一个整数。 现在你释放这两个字符,然后请求一个新的整数,但是这个整数不能放在两个字符的空间中。 该内存不能被重新使用,因为它没有足够大的连续块来重新分配。 最重要的是,你已经为你的字符调用了大量的分配器开销。

本质上,内存只能在大多数系统上以特定大小的块进行。 一旦你把这些块分开,它们就不能被重新join,直到整个块被释放。 当实际上只有块的一小部分被使用时,这可能导致整个块被使用。

减less堆碎片的主要方法是做更大,更不频繁的分配。 在极端情况下,您可以使用托pipe堆,至less可以在自己的代码中移动对象。 无论如何,这从内存的angular度完全消除了这个问题。 显然移动物体等都有成本。 事实上,如果你经常从堆中分配非常less量的数据,那么你只有真正的问题。 使用连续的容器(向量,string等)并尽可能多地分配到堆栈上(对于性能来说,总是一个好主意)是减less堆栈的最好方法。 这也增加了caching一致性,这使得您的应用程序运行速度更快。

你应该记住的是,在一个32位的x86桌面系统上,你有整个2GB的内存,它被分成4KB“页面”(很确定所有x86系统的页面大小都是一样的)。 你将不得不调用一些omgwtfbbq碎片有一个问题。 碎片化确实是过去的问题,因为现代堆栈对于绝大多数应用程序来说过于庞大,并且存在能够抵抗它的系统的普遍性,比如堆积如山。