我知道有很多关于我在这里指出的问题的问题,但我找不到任何复杂的答案(无论是在StackOverflow上还是在其他来源中)。
我想问一下堆(RAM)碎片问题。
据我了解,有两种碎片:内部- 与分配单元大小 (AU) 和分配内存大小 AM 之间的差异有关(浪费内存等于 AM % AU),外部- 与可用内存的非连续区域相关,因此即使可用内存区域的总和可以处理新的分配请求,如果没有可以处理它的继续区域,它也会失败。
这是很清楚的。当"分页"出现时,问题就开始了。
有时我可以找到分页解决外部碎片问题的信息。 事实上,我同意,由于分页,操作系统能够创建分配给进程的内存的虚拟连续区域,即使内存的各个部分在物理上分散。
但是,它究竟如何帮助解决外部碎片化呢?我的意思是,假设页面的大小有 4kB,并且我们想要分配 16 kB,那么我们当然只需要找到四个空页面框架,即使物理上这些框架不是连续区域的一部分。
但是在分配较小的情况下呢?我相信页面本身仍然可以碎片化,并且(在最坏的情况下)如果旧帧不能用于分配请求的内存,操作系统仍然需要提供新帧。
那么(假设最坏的情况)迟早会因为外部碎片而分配和释放堆内存(不同大小)的长时间工作的应用程序会陷入内存不足的情况吗?
那么问题是如何应对外部碎片化?自己实现分配算法?分页(正如我所写,不确定是否有帮助)?还有什么?操作系统(Windows,Linux)是否提供一些碎片整理方法?
最激进的解决方案是禁止使用堆,但是对于具有分页、虚拟地址空间、虚拟内存等的平台来说,真的有必要吗......唯一的问题是应用程序需要 不可阻挡地运行一年 ?
还有一个问题..内部碎片是一个模棱两可的术语吗?我在某处发现了内部碎片指向页面框架部分的定义,这是浪费的,因为进程不需要更多内存,但单个帧不能由多个进程拥有。
我把问题加粗了,所以赶时间的人可以在不阅读所有内容的情况下找到问题。
问候!
碎片化"确实不是一个非常精确的术语。但我们可以肯定地说,当正在运行的应用程序需要n
字节块并且有n
个或更多字节未使用,但我们无法获得所需的块时,那么"内存太碎片化了"。
但是它究竟如何[分页]帮助外部分配[我假设你的意思是碎片]?
这里真的没有什么复杂的。外部碎片是分配的块之间的可用内存,"太小",无法满足任何应用程序要求。这是一个一般概念。"太小"的定义取决于应用程序。尽管如此,如果分配的区块可以落在任何边界上,那么在多次分配和解除分配之后,很容易发生大量这样的碎片。分页以两种方式帮助解决外部碎片。
首先,它将内存细分为固定大小的相邻块 - 页面 - 这些块"足够大",因此它们永远不会无用。同样,"足够大"的定义并不准确。但是大多数应用程序将具有单个 4k 页面可以满足的许多要求。由于分配页面或更少的页面时不会出现外部碎片问题,因此该问题已得到缓解。
其次,分页硬件在应用程序页和物理内存页之间提供间接级别。因此,任何空闲的物理内存页都可用于帮助满足任何应用程序请求,无论其大小如何。例如,假设您有 100 个物理页面,并且每隔一个物理页面(其中 50 个)分配一次。如果没有页面映射硬件,可以满足的对连续内存的最大请求是 1 页。使用映射,它是 50 页。(我忽略了最初分配的虚拟页面,没有映射的物理页面。这是另一个讨论。
但是在分配较小的情况下呢?
同样,这很简单。如果分配单位是页面,则任何小于页面的分配都会产生未使用的部分。这是内部碎片:分配的块中不可用的内存。分配单元越大(它们不必是单个页面),由于内部碎片而无法使用的内存就越多。平均而言,这将趋向于分配单元的一半。因此,尽管操作系统倾向于以页面为单位进行分配,但大多数应用程序端内存分配器从操作系统请求非常少量(通常是一个)大块(页面)。它们在内部使用更小的分配单元:4-16 字节很常见。
所以问题是如何处理外部分配[我假设你的意思是碎片化]?那么(假设最坏的情况)迟早会因为外部碎片而分配和释放堆内存(不同大小)的长时间工作的应用程序会陷入内存不足的情况吗?
如果我理解正确,你是在问分裂是否不可避免。除非在非常特殊的条件下(例如,应用程序只需要一种大小的块),答案是肯定的。但这并不意味着它一定是一个问题。
内存分配器使用智能算法来非常有效地限制碎片。例如,他们可以维护具有不同块大小的"池",使用块大小与给定请求最匹配的池。这往往会限制内部和外部碎片。一个有据可查的真实例子是dlmalloc。源代码也非常清楚。
当然,任何通用分配器在特定条件下都可能失败。出于这个原因,现代语言(我知道C++和 Ada 是两种)允许您为给定类型的对象提供专用分配器。通常- 对于固定大小的对象 - 这些可能只是维护一个预涂鸦的空闲列表,因此该特定情况的碎片为零,并且分配/释放非常快。
还有一点需要注意:通过复制/压缩垃圾收集可以完全消除碎片。当然,这需要底层语言支持,并且需要支付性能费用。 复制垃圾回收器通过移动对象来压缩堆,以便在运行时完全消除未使用的空间以回收存储。为此,它必须将正在运行的程序中的每个指针更新到相应对象的新位置。虽然这听起来很复杂,但我已经实现了一个复制垃圾收集器,而且还不错。算法非常酷。不幸的是,许多语言(例如 C 和 C++)的语义不允许在正在运行的程序中查找每个指针,这是必需的。
最激进的解决方案是禁止使用堆,但是对于具有分页、虚拟地址空间、虚拟内存等的平台来说,真的有必要吗......唯一的问题是应用程序需要 不可阻挡地运行一年 ?
虽然通用分配器很好,但它们不能保证。对于安全关键或硬实时受限的系统来说,完全避免堆使用并不罕见。 另一方面,当不需要绝对保证时,通用分配器通常没问题。有许多系统使用通用分配器长时间在强硬负载下完美运行:碎片达到可接受的稳定状态并且不会引起问题。
还有一个问题..内部碎片是一个模棱两可的术语吗?
该术语不是模棱两可的,而是在不同的上下文中使用。不变的是它指的是分配块中未使用的内存。
操作系统文献倾向于假设分配单位是页面。例如,Linux sbrk 允许您请求在任何地方设置数据段的末尾,但 Linux 分配页面而不是字节,因此从操作系统的角度来看,最后一页的未使用部分是内部碎片。
面向应用程序的讨论倾向于假设分配是任意大小的"块"或"块"。 DLMALLOC 使用大约 128 个离散的块大小,每个大小都维护在自己的自由列表中。此外,它将使用操作系统内存映射系统调用自定义分配非常大的块,因此请求和实际分配之间最多存在页面大小(减去 1 个字节)不匹配。显然,尽量减少内部碎片会很麻烦。给定分配导致的碎片是请求与实际分配的块之间的差异。由于块大小如此之多,因此这种差异受到严格限制。另一方面,许多块大小增加了外部碎片问题的可能性:可用内存可能完全由 dlmalloc 管理良好的块组成,但太小而无法满足应用程序要求。