如何解决内存碎片

Posted

技术标签:

【中文标题】如何解决内存碎片【英文标题】:How to solve Memory Fragmentation 【发布时间】:2010-09-08 19:40:50 【问题描述】:

我们偶尔会遇到问题,即我们的长时间运行的服务器进程(在 Windows Server 2003 上运行)由于内存分配失败而引发异常。我们怀疑这些分配由于内存碎片而失败。

因此,我们一直在寻找一些可能对我们有所帮助的替代内存分配机制,我希望有人能告诉我最好的:

1) 使用 Windows Low-fragmentation Heap

2) jemalloc - 用于Firefox 3

3) Doug Lea 的malloc

我们的服务器进程是使用跨平台 C++ 代码开发的,因此理想的解决方案也是跨平台的(*nix 操作系统会遭受这种类型的内存碎片吗?)。

另外,我认为 LFH 现在是 Windows Server 2008 / Vista 的默认内存分配机制是否正确?...如果我们的客户只是升级他们的服务器操作系统,我当前的问题会“消失”吗?

【问题讨论】:

【参考方案1】:

在怀疑碎片之前我会怀疑泄漏。

对于内存密集型数据结构,您可以切换到可重用的存储池机制。与堆相比,您还可以在堆栈上分配更多的东西,但实际上我认为这不会产生太大的影响。

我会启动一个像 valgrind 这样的工具,或者做一些密集的日志记录来寻找没有被释放的资源。

【讨论】:

【参考方案2】:

您可以通过减少分配释放的数量来帮助减少碎片。

例如例如,对于运行服务器端脚本的 Web 服务器,它可能会创建一个字符串来输出页面。而不是为每个页面请求分配和释放这些字符串,只需维护它们的池,因此您只在需要更多时分配,但您不释放(意味着一段时间后您会遇到不再分配的情况,因为您有够了)

您可以使用 _CrtDumpMemoryLeaks();在运行调试版本时将内存泄漏转储到调试窗口,但是我相信这是特定于 Visual C 编译器的。 (在 crtdbg.h 中)

【讨论】:

【参考方案3】:

@nsaners - 我很确定问题出在内存碎片上。我们分析了minidumps,指出在分配大块(5-10mb)内存时存在问题。我们还监控了进程(现场和开发中)以检查内存泄漏 - 没有检测到(内存占用通常很低)。

【讨论】:

你必须小心你的结论。即使是非常小的泄漏也可能导致堆的大量碎片。将每个未释放的 1 字节泄漏 malloc() 视为堆中的弹孔。紧邻的方块永远不会合并。【参考方案4】:

这个问题确实发生在 Unix 上,虽然它通常没有那么严重。

低碎片化堆帮助了我们,但我的同事发誓Smart Heap (多年来,它已在我们的一些产品中跨平台使用)。不幸的是,由于其他情况,我们这次无法使​​用 Smart Heap。

我们还研究了块/分块分配并尝试拥有精通范围的池/策略,即 这里是长期的事情,那里是整个请求的事情,那里是短期的事情,等等。

【讨论】:

【参考方案5】:

像往常一样,您通常可以浪费内存来获得一些速度。

这种技术对通用分配器没有用处,但它确实有它的位置。

基本上,这个想法是编写一个分配器,它从所有分配大小相同的池中返回内存。这个池永远不会碎片化,因为任何块都和另一个块一样好。您可以通过创建具有不同大小块的多个池来减少内存浪费,并选择仍然大于请求数量的最小块大小池。我已经使用这个想法来创建在 O(1) 中运行的分配器。

【讨论】:

我不同意这个想法。增加所有分配的大小会产生内部碎片。如果您可以浪费内存,最好只增加堆的总大小 - 这本身可能会避免碎片成为问题。即:完全避免堆不稳定【参考方案6】:

首先,我同意其他建议资源泄漏的海报。你真的想先排除这种可能性。

希望您当前使用的堆管理器能够转储堆中的实际可用总空闲空间(跨所有 空闲 块)以及它的块总数分了。如果与堆中的总空闲空间相比,平均空闲块大小相对较小,则确实存在碎片问题。或者,如果您可以转储最大空闲块的大小并将其与总空闲空间进行比较,那将完成同样的事情。如果您遇到碎片,最大的空闲块相对于所有块中可用的总可用空间会很小。

为了清楚地说明上述内容,在所有情况下,我们都在讨论堆中的空闲块,而不是堆中分配的块。无论如何,如果不满足上述条件,那么您确实有某种泄漏情况。

因此,一旦您排除了泄漏,您可以考虑使用更好的分配器。问题中建议的 Doug Lea 的 malloc 对于一般用途的应用程序来说是一个非常好的分配器,并且大多数时间都非常健壮。换句话说,它已经过时间测试,可以很好地适用于大多数应用程序。然而,没有一种算法适合所有应用程序,任何管理算法方法都可能被与其设计相悖的病态条件破坏。

您为什么会遇到碎片问题? - 碎片问题的根源是由应用程序的行为引起的,并且与同一应用程序的分配生命周期大不相同记忆竞技场。也就是说,一些对象会定期分配和释放,而其他类型的对象会在同一个堆中持续较长时间......将较长生命周期的对象视为在竞技场的更大区域中戳洞,从而防止合并已释放的相邻块。

要解决此类问题,您可以做的最好的事情是在逻辑上将堆划分为生命周期更相似的子区域。实际上,您需要一个临时堆和一个或多个持久堆,它们将具有相似生命周期的事物组合在一起。

其他一些人提出了另一种解决该问题的方法,即尝试使分配大小更相似或相同,但这不太理想,因为它会创建一种称为内部碎片的不同类型的碎片 - 这实际上是浪费通过在块中分配比您需要的更多的内存来获得空间。

此外,使用像 Doug Lea 那样好的堆分配器,使块大小更相似是不必要的,因为分配器已经在执行两倍大小的分桶方案,这将完全没有必要人为地调整传递的分配大小到 malloc() - 实际上,他的堆管理器会自动为您执行此操作,比应用程序能够进行的调整要强大得多。

【讨论】:

我喜欢同时拥有临时堆和持久堆的想法。这似乎类似于现代垃圾收集器中的“生成”概念。这个想法是否在手动内存分配方案中广泛实施? @Waylon Flinn - 创建子堆是嵌入式系统中内存碎片问题的非常常见的解决方案。或者,许多嵌入式系统试图通过尽可能多地预先分配并且从不释放这些较长生命的对象来解决这些问题……实际上是静态分配,至少在某种程度上是这样。如果您有足够的内存在系统的生命周期内专用于特定子系统,那么这不是一个坏方法。【参考方案7】:

正如您所建议的,Doug Lea 的 malloc 可能运行良好。它是跨平台的,并且已用于运输代码。至少,它应该很容易集成到您的代码中进行测试。

在固定内存环境中工作多年,这种情况肯定是个问题,即使在非固定环境中也是如此。我们发现 CRT 分配器在性能(速度、浪费空间的效率等)方面往往很糟糕。我坚信,如果您在很长一段时间内对良好的内存分配器有广泛的需求,您应该编写自己的(或者看看像 dlmalloc 这样的东西是否可以工作)。诀窍是编写适合您的分配模式的东西,并且几乎与其他任何事情一样与内存管理效率有关。

试试 dlmalloc。我肯定会竖起大拇指。它也相当可调,因此您可以通过更改一些编译时间选项来提高效率。

老实说,您不应该依赖新的操作系统实现“消失”的东西。 N 年后的服务包、补丁或另一个新操作系统可能会使问题变得更糟。同样,对于需要强大内存管理器的应用程序,不要使用编译器提供的库存版本。找到适合您的情况的。从 dlmalloc 开始并对其进行调整,看看您是否可以获得最适合您的情况的行为。

【讨论】:

【参考方案8】:

我认为您错误地过早地排除了内存泄漏。 即使是很小的内存泄漏也会导致严重的内存碎片。

假设您的应用程序的行为如下: 分配 10MB 分配 1 个字节 免费 10MB (哎呀,我们没有释放 1 个字节,但谁在乎 1 个小字节)

这似乎是一个非常小的泄漏,仅监视分配的总内存大小时几乎不会注意到它。 但是这种泄漏最终会导致您的应用程序内存看起来像这样: . . 免费 – 10MB . . [分配的 -1 字节] . . 免费 – 10MB . . [分配的 -1 字节] . . 免费 – 10MB . .

不会注意到此泄漏...直到您要分配 11MB 假设您的 minidump 包含完整的内存信息,我建议使用 DebugDiag 来发现可能的泄漏。 在生成的内存报告中,仔细检查分配计数(而不是大小)

【讨论】:

【参考方案9】:

如果您谈论的是 Win32 - 您可以尝试使用 LARGEADDRESSAWARE 压缩某些内容。您将拥有约 1Gb 的额外碎片整理内存,因此您的应用程序会将其碎片化时间更长。

【讨论】:

【参考方案10】:

简单、快速和肮脏的解决方案是将应用程序拆分为多个进程,每次创建进程时都应该获得新的 HEAP。

您的内存和速度可能会受到一点影响(交换),但快速的硬件和大内存应该能够提供帮助。

当线程还不存在时,这是带有守护进程的旧 UNIX 技巧。

【讨论】:

以上是关于如何解决内存碎片的主要内容,如果未能解决你的问题,请参考以下文章

处理内存池中的碎片?

如何减小内存碎片

内存碎片问题

是啥导致 .NET 中的内存碎片

linux内存碎片防治技术

面试题:Linux是如何避免内存碎片的