
Linux 系统长期运行后,会频繁出现内存充足却无法分配大段连续内存的诡异问题,伴随 OOM 触发、进程卡顿、服务抖动等现象。绝大多数人排查内存故障只会看使用率、缓存、Swap 状态,却忽略了内存碎片这一核心元凶,而解决内存碎片化的关键内核机制,正是内存规整。作为 Linux 内存调优的核心底层能力,内存规整负责整理离散的空闲内存页,合并碎片化内存,为系统提供连续物理内存,是高性能服务、长稳运行业务必须掌握的知识点。
很多开发者日常调优只停留在参数层面,完全不懂内存规整的触发时机、运作逻辑、优缺点以及阻塞开销,导致线上调优治标不治本。本文带你从零吃透内存规整核心原理,详解主动规整、被动规整的区别与适用场景,剖析内存规整如何解决系统内存碎片化问题。真正掌握 Linux 深层次内存调优技巧,彻底解决线上内存碎片、大内存分配失败、系统卡顿等疑难问题。
一、初识内存规整
面试题写作模版内存规整,简单来说,就是对内存中的数据进行重新排列,将分散的空闲内存块合并成连续的大内存块,从而减少内存碎片,提高内存利用率。它就像是你整理书架,把零散摆放的书籍重新排列,让书架上出现更多的连续空间,以便能放下更大部头的书籍。在 Linux 系统中,内存规整是通过内核中的内存管理模块来实现的,它主要针对物理内存中的外部碎片问题进行处理。
内存规整的实现依赖于页迁移(Page Migration),页迁移可以说是内存规整的核心操作。简单来说,页迁移就是将一个物理页面的数据从一个位置移动到另一个位置,同时更新相关的进程映射信息,以确保进程能够正确地访问迁移后的页面 。这个过程并不简单,涉及到数据的拷贝和复杂的映射关系更改。
当进行页迁移时,内核首先要为目标页面分配新的物理页框,然后将旧页面中的数据逐字节地拷贝到新页面中 。在数据拷贝完成后,内核还需要更新所有映射到旧页面的页表项,将它们指向新的页面,以保证进程在访问该页面时能够找到正确的数据。此外,如果有多个进程同时映射到该页面,还需要协调好各个进程的映射关系,确保数据的一致性和完整性。
可以把页迁移想象成搬家,我们要把一个房间里的所有物品搬到另一个房间,不仅要把物品搬运过去,还要重新整理和布置新房间,让所有使用这个房间的人都能顺利找到他们需要的东西 。这个过程需要耗费一定的时间和系统资源,所以内存规整通常是在内存分配遇到困难,且有必要进行时才会触发,以避免频繁的页迁移对系统性能造成过大的影响 。
在 Linux 内核中,内存规整通常是以 zone 为单位进行的 。zone 是 Linux 内存管理中的一个重要概念,它将物理内存按照不同的属性和用途划分为不同的区域,比如 DMA zone 用于直接内存访问设备,Normal zone 用于普通的内存分配,HighMem zone 用于高端内存等 。

为了实现以 zone 为单位的内存规整,内核封装了 compact_zone 接口,它是内存规整的核心接口,负责对指定的 zone 进行内存规整操作 。当需要对某个 zone 进行内存规整时,内核会调用 compact_zone 接口,并传入相关的参数,如 zone 的指针、内存规整的优先级、需要分配的内存块大小等信息,compact_zone 接口会根据这些参数,对 zone 内的内存进行整理,尝试腾出连续的内存空间。
不过,内存规整中有一个特殊情况,就是 alloc_contig_range 函数,它用于分配指定地址区域的内存 。当该函数发现指定地址区域的内存被占用时,会尝试对这段内存进行规整迁移,它并非针对 zone 的规整,而是针对指定内存区域的规整,其规整类型与主动内存规整类似,但实现细节有所不同 。这种针对特定区域的内存规整方式,为一些对内存地址有特殊要求的场景提供了灵活的解决方案 。
二、内存规整的基本原理
面试题写作模版内存碎片化是在内存分配和释放过程中产生的一种不良现象,就像我们整理书架时,不断地放入和取出书籍,时间久了,书架上就会出现一些零散的小空位,这些空位难以再用来放置一本完整的新书,内存碎片化也是如此。它主要分为外部碎片和内部碎片两种类型,各自有着独特的产生原因和影响。
外部碎片是指内存中存在许多大小不一、分布零散的空闲内存块 ,虽然这些空闲内存块的总和可能很大,但由于它们的不连续,导致无法满足对较大内存块的分配请求。例如,假设内存中有三个空闲块,大小分别为 2KB、3KB 和 5KB ,而此时有一个程序需要申请 4KB 的连续内存块,尽管总的空闲内存量(2KB + 3KB + 5KB = 10KB)足够,但由于这些空闲块是分散的,无法提供连续的 4KB 空间,这就产生了外部碎片,导致内存分配失败。
外部碎片通常是由于频繁地分配和释放不同大小的内存块所导致的。当内存分配器不断地处理各种大小的内存请求时,随着时间的推移,内存空间就会变得越来越零散,就像反复在一块土地上建造和拆除不同大小的房屋,最后土地上只剩下一些不规则的小块空地,难以再进行大规模的建设。
内部碎片则是指在已分配的内存块内部,存在未被使用的空间,从而造成了内存的浪费。这种情况通常发生在内存分配策略导致分配的内存块比实际请求的内存稍大时。比如,在某些内存分配机制中,为了满足特定的对齐要求或者由于内存分配单位的限制,当程序申请一个较小的内存空间时,实际分配的内存块可能会比请求的内存大一些。
例如,假设内存分配单位是 8KB,而程序只需要 6KB 内存,那么分配的 8KB 中有 2KB 是浪费的,这 2KB 就是内部碎片。再比如,在一个结构体中,由于编译器为了保证数据成员在内存中的对齐,可能会在结构体成员之间插入一些填充字节,导致结构体实际占用的内存空间大于其成员所需的内存空间,这些多出来的填充字节就是内部碎片的一种表现形式 。
内存碎片化对系统性能有着显著的负面影响。它会导致内存分配失败的概率增加,当系统中存在大量的内存碎片时,即使总的空闲内存量充足,也可能无法为某些需要较大连续内存空间的程序或数据结构提供足够的内存,从而影响程序的正常运行。内存碎片化还会增加内存管理的复杂度,内存分配器在寻找合适的空闲内存块时,需要花费更多的时间和资源去遍历和匹配这些零散的空闲块,这会降低内存分配和释放的效率,进而影响整个系统的性能。在一些对内存性能要求较高的场景,如大型数据库系统、实时控制系统等,内存碎片化可能会导致系统响应变慢、吞吐量降低,甚至出现系统崩溃等严重问题。
内存规整的核心目标就是全力解决内存碎片化这一棘手问题,通过一系列精心设计的方法和策略,大幅提高内存的利用率,确保系统能够高效、稳定地运行。内存碎片化就如同在繁忙的城市中,土地被分割成了许多零散的小块,难以进行大规模的建设和规划。而内存规整则像是城市规划师,对这些零散的土地进行重新整理和规划,将它们整合为更大、更规整的区域,以便更好地利用。
在实际的计算机系统运行过程中,许多程序和应用都对连续的内存空间有着强烈的需求。例如,一些大型的数据库管理系统在处理大量数据时,需要连续的内存空间来高效地存储和查询数据;图形处理软件在渲染复杂的图像时,也需要连续的内存来存储图像数据和进行计算。如果内存中存在大量的碎片,这些程序就可能无法获得足够的连续内存,导致性能下降甚至无法正常工作。内存规整通过对内存中的数据进行重新排列和整理,将那些零散的空闲内存块合并成连续的大块内存,就像把城市中零散的小块土地合并成大片的可建设用地,从而满足系统对连续内存的迫切需求。
内存规整还可以减少内存管理的开销。当内存碎片化严重时,内存分配器需要花费更多的时间和精力去管理和维护这些零散的内存块,包括记录空闲块的位置、大小等信息,以及在分配和释放内存时进行复杂的查找和匹配操作。而通过内存规整,内存空间变得更加连续和有序,内存分配器可以更高效地进行内存管理,减少管理开销,提高系统的整体性能。内存规整还可以提高内存的回收效率,及时释放那些不再使用的内存空间,避免内存资源的浪费,进一步提升内存的利用率。
(1)页面合并:页面合并是内存规整中一种非常基础且重要的方法,其核心原理是将相邻的空闲页面巧妙地合并为一个更大的连续空闲区域,从而有效地减少外部碎片的产生。在计算机的内存管理中,内存通常被划分为一个个固定大小的页面,这些页面就像是一块块整齐排列的小砖块。当程序运行过程中不断地进行内存分配和释放操作时,内存中会出现许多零散的空闲页面,这些空闲页面就如同散落在各处的小砖块,难以被充分利用。
页面合并就像是一个勤劳的建筑工人,将这些相邻的空闲小砖块(空闲页面)一块一块地拼接起来,形成一个更大的连续空间。例如,假设内存中有两个相邻的空闲页面 A 和 B,它们原本是独立的小块空闲区域,通过页面合并技术,系统可以将这两个页面合并成一个更大的连续空闲区域,这样在后续的内存分配过程中,就可以为需要较大连续内存的程序提供更合适的空间,大大减少了因外部碎片导致的内存分配失败的情况。
页面合并的过程通常需要内存管理系统对内存中的页面状态进行精确的跟踪和记录,当检测到相邻的空闲页面时,就触发合并操作,将它们整合为一个更大的空闲块,并更新内存管理数据结构中的相关信息,以确保系统能够准确地管理这个新的连续空闲区域。
(2)物理页面压缩:物理页面压缩是一种通过内存回收和页面压缩技术来减少不必要内存占用的有效方法,它就像是一个神奇的压缩器,能够将内存中的数据进行 “压缩打包”,从而腾出更多的内存空间。在计算机系统运行时,内存中会存储各种各样的数据,有些数据可能在一段时间内不会被频繁访问,但它们却仍然占用着宝贵的内存资源。
内存回收机制会定期检查内存中的数据,将那些长时间未被访问或者不再需要的数据从内存中移除,释放它们所占用的内存空间。而页面压缩技术则是在数据被回收之前,对内存页面中的数据进行压缩处理。通过采用高效的压缩算法,如 LZ4、ZSTD 等,将页面中的数据压缩成更小的体积,然后将压缩后的数据重新存储在内存中。这样,原本占用较大内存空间的页面,经过压缩后就可以占用更少的内存,从而有效地减少了内存的占用量。
例如,一个原本占用 4KB 内存空间的页面,经过压缩后可能只需要 1KB 的空间,这样就释放出了 3KB 的内存空间,这些释放出来的空间可以被重新分配给其他需要内存的程序或任务。物理页面压缩不仅可以减少内存占用,还可以在一定程度上提高内存的访问效率,因为在读取压缩后的数据时,虽然需要进行解压缩操作,但由于数据量减少,从内存中读取数据的时间也会相应缩短,从而提升了系统的整体性能。
(3)页表压缩:页表是操作系统用于管理虚拟内存和物理内存之间映射关系的数据结构,它就像是一本详细的地址映射字典,记录着每个虚拟地址对应的物理地址。在现代计算机系统中,随着内存容量的不断增大和虚拟地址空间的扩展,页表的规模也变得越来越庞大,这不仅会占用大量的内存空间,还会增加内存管理的复杂性和开销。
页表压缩的核心原理就是对页表的数据结构进行优化,通过采用更紧凑的存储方式和高效的算法,减少页表所占用的内存空间,从而提高内存管理的效率。一种常见的页表压缩方法是使用多级页表结构,将原本庞大的页表按照层次结构进行划分,只有在需要访问特定层级的页表时才将其加载到内存中,这样可以避免一次性将整个庞大的页表都加载到内存中,减少了内存的占用。还可以采用一些特殊的编码方式和数据压缩算法,对页表中的映射信息进行压缩存储。
例如,对于一些连续的虚拟地址到物理地址的映射关系,可以采用简洁的编码方式来表示,而不是为每个映射关系都存储完整的地址信息,从而减少了页表的数据量。通过页表压缩,不仅可以节省大量的内存空间,还可以加快页表的查找速度,因为在较小的页表中查找特定的映射关系会更加快速和高效,进而提高了内存管理的效率,减少了系统在内存管理方面的开销,提升了整个系统的性能。
(4)内存回收:内存回收是内存规整中一个至关重要的机制,其主要作用是及时释放那些不再被使用的内存区域,避免这些区域长时间占用内存资源,从而造成内存的浪费。在计算机系统中,当一个程序运行时,它会向操作系统申请一定的内存空间来存储数据和执行代码。当这个程序执行完毕或者不再需要某些内存区域时,这些内存区域就应该被及时回收,以便其他程序或任务能够使用这些释放出来的内存。
内存回收机制会对内存的使用情况进行实时监测和管理。它会定期扫描内存,检查各个内存区域的使用状态,判断哪些内存区域已经不再被任何程序所引用或者访问。一旦发现这样的 “闲置” 内存区域,内存回收机制就会立即采取行动,将这些内存区域标记为可回收状态,并将其从当前使用的内存列表中移除。然后,这些被回收的内存空间就可以被重新分配给其他有需求的程序或任务。
例如,当一个用户关闭了正在运行的文档编辑软件时,该软件所占用的内存区域就会被内存回收机制检测到,然后系统会将这些内存区域回收,并将其重新纳入到空闲内存池,以便后续其他程序如浏览器、音乐播放器等在需要内存时能够从中获取。内存回收不仅可以提高内存的利用率,还可以减少内存碎片化的程度。因为及时回收不再使用的内存,可以避免这些内存区域成为零散的碎片,保持内存空间的相对连续性,为后续的内存分配和内存规整操作提供更好的基础。
三、内存规整的触发机制
面试题写作模版直接内存规整是在内存分配的关键时刻被触发的一种应急机制,它就像是内存管理系统中的 “急救医生”,在常规内存分配手段失效时迅速介入,以解决内存分配面临的困境。
当系统中的某个程序或进程向内存分配器请求内存时,内存分配器首先会按照常规的分配策略,从空闲内存列表中寻找合适的内存块来满足请求。这一过程就如同在一个整齐有序的仓库中,根据货物的大小和形状,快速地找到合适的存放位置。通常情况下,内存分配器会以 low 水线为基准调用 get_page_from_freelist 函数尝试进行内存分配,这个过程相对高效且直接。
// 快速分配路径:尝试从空闲链表获取页面page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);然而,在实际的系统运行中,由于频繁的内存分配和释放操作,内存空间可能会变得碎片化,就像仓库里的货物被频繁地搬运和堆放,导致空间变得杂乱无章。当这种碎片化情况严重时,即使总的空闲内存量看起来足够,但由于空闲内存被分割成了许多零散的小块,无法提供满足请求所需的连续内存空间,常规的内存分配就会失败。此时,系统就会进入慢速内存分配流程,即__alloc_pages_slowpath 函数。
// 分配失败后进入慢速路径page = __alloc_pages_slowpath(gfp_mask, order, ac);在慢速内存分配流程中,系统会尝试一系列措施来解决内存分配问题。首先,系统会尝试唤醒 kswapd 内核线程进行内存回收,kswapd 就像是仓库的清理工,负责将那些长时间未被使用的 “货物”(内存页面)清理出去,腾出更多的空闲空间。但是,系统并不会等待 kswapd 完成内存回收后再继续分配,而是立即调用 get_page_from_freelist 函数再次尝试内存分配,不过这次是以 min 水线为基准进行尝试。
如果这次尝试仍然失败,系统就会根据 gfp 标识来确认当前的内存分配请求是否支持直接内存回收。如果支持,系统就会调用__alloc_pages_direct_compact 函数,尝试进行第一次直接内存规整以及内存分配。在首次调用直接内存规整时,其优先级通常设置为 INIT_COMPACT_PRIORITY,这个优先级会影响内存规整触发页迁移的类型,比如 INIT_COMPACT_PRIORITY 对应的就是 MIGRATE_ASYNC 即异步迁移类型。
异步迁移的特点是在页迁移时不会阻塞其他操作,这就像是在仓库中搬运货物时,不会因为搬运操作而停止其他货物的进出,但是这种方式的规整或迁移能力相对较弱。
// 直接内存规整(内核慢速分配路径调用)page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac,INIT_COMPACT_PRIORITY, &contended_compact);如果第一次直接内存规整后仍然无法成功分配内存,系统就会进入一个循环调用流程,不断地唤醒 kswapd、调用 get_page_from_freelist、进行__alloc_pages_direct_reclaim(直接页面回收)以及__alloc_pages_direct_compact(直接内存规整)。在这个循环过程中,每次调用直接内存规整时,其规整优先级可能会逐步降低(越低对应规整强度越高),从而提升内存规整的效用。
例如,随着循环的进行,内存规整可能会从最初的异步迁移逐渐转变为阻塞规整,阻塞规整虽然会暂时阻塞其他操作,但能够更有效地进行内存规整,就像在仓库中进行大规模的货物整理时,暂时停止其他货物的进出,以便更彻底地整理空间。不过,这种不断循环调用和提高规整强度的过程也会带来较高的性能代价,因为每次内存规整都涉及到数据的迁移和页表的更新等复杂操作,所以直接内存规整是一种在内存分配面临紧急情况时才采取的 “强力措施”,旨在通过尽可能地整理内存碎片,为内存分配请求提供连续的内存空间,确保系统的正常运行。
被动内存规整是在系统内存压力较大,面临内存短缺风险时被触发的一种内存管理机制,它就像是在暴风雨中为船只调整货物布局的船员,通过重新整理内存资源,确保系统这艘 “大船” 能够在内存紧张的 “波涛” 中稳定前行。
在计算机系统持续运行的过程中,随着各种程序和进程的不断启动、运行和结束,内存的使用情况会变得日益复杂。当系统中运行的程序数量众多,或者某些程序对内存的需求异常庞大时,系统的内存资源会逐渐变得紧张。此时,系统会密切监测内存的使用状态,当发现内存压力达到一定阈值,例如空闲内存量低于某个设定的临界值,或者内存的分配请求频繁失败时,就会触发被动内存规整。
kswapd 内核线程在被动内存规整中扮演着关键角色。kswapd 会定期检查内存节点中的各个内存区域(zone),判断它们是否处于平衡状态,这里的平衡状态通常以 WMARK_HIGH 水位线为参考标准。WMARK_HIGH 就像是内存区域的一个 “安全水位”,当内存使用量低于这个水位时,内存区域被认为处于相对稳定的状态。如果 kswapd 发现某个内存区域的内存使用量低于 WMARK_HIGH,即内存处于不平衡状态,且内存压力较大时,就会触发对该内存区域的被动内存规整。
// kswapd 检查区域是否平衡,不平衡则触发规整if (!zone_balanced(zone, zonelist, order, 0, classzone_idx)) compact_zone_order(zone, order, GFP_KERNEL, &contended);在被动内存规整过程中,系统会对内存中的页面进行重新整理和迁移。与直接内存规整不同,被动内存规整的目标不仅仅是为了满足某个特定的内存分配请求,而是更侧重于整体上缓解内存压力,减少内存碎片,提高内存的利用率。系统会根据内存页面的可移动性将它们分为不同的类型,例如可移动页面(主要是用户程序分配的内存,移动这类页面只需修改页表映射关系,代价相对较低)、可回收页面(不可移动但可以释放的内存)和不可移动页面(如内核当前正在使用的物理页面)。
然后,系统会优先迁移可移动页面,将它们从内存中较为零散的位置移动到连续的区域,就像将仓库中零散放置的货物集中搬运到一个区域,从而腾出更多连续的空闲内存空间。这些连续的空闲内存空间可以更好地满足后续可能出现的内存分配需求,无论是来自新启动的程序,还是现有程序的内存扩充请求。
被动内存规整还会结合内存回收机制,将那些长时间未被访问或者不再被使用的内存页面及时释放,进一步增加系统的空闲内存量。通过这种方式,被动内存规整有效地缓解了系统的内存压力,提高了内存的整体使用效率,保障了系统在内存紧张情况下的稳定运行,避免了因内存不足而导致的系统崩溃或程序异常终止等问题。
预应性内存规整是一种极具前瞻性的内存管理策略,它就像是一位经验丰富的航海家,在暴风雨来临之前就提前调整好船只的状态,系统通过对内存使用趋势的精准分析和预测,提前采取内存规整措施,以预防内存碎片化问题的恶化,确保内存资源能够持续高效地满足系统的运行需求。
在计算机系统的运行过程中,内存的使用情况并非是完全随机和不可预测的。随着时间的推移,通过对系统中各种程序和进程的内存分配与释放模式进行长期监测和分析,系统可以逐渐掌握内存使用的规律和趋势。例如,某些应用程序在启动时会大量申请内存,然后在运行过程中周期性地释放和重新分配内存;而另一些程序则可能在长时间运行中持续占用大量内存。预应性内存规整正是基于对这些内存使用趋势的深入理解而实施的。
系统会利用专门的算法和工具来收集和分析内存使用数据,预测未来一段时间内内存的需求和碎片化情况。当系统预测到内存碎片化程度可能会在未来某个时刻达到影响系统性能的阈值,或者预计即将出现大规模的内存分配需求,而当前的内存状态可能无法满足时,就会主动触发预应性内存规整。
// 内核基于预测触发 proactive 内存规整if (should_proactive_compact(zone)) queue_work(system_unbound_wq, &zone->compact_work);与其他内存规整方式相比,预应性内存规整具有显著的优势。它能够在内存问题实际发生之前就采取措施,避免了因内存碎片化严重导致的内存分配失败和系统性能下降等问题。通过提前进行内存规整,系统可以保持内存的连续性和高效利用,为后续的内存分配操作提供更良好的基础。
这就像在仓库中,提前根据货物进出的规律和未来的需求,对货物进行合理的整理和布局,使得在后续有货物进出时,能够更快速、高效地完成操作。预应性内存规整还可以减少因内存紧张而触发的其他更具开销的内存管理操作,如频繁的内存回收和直接内存规整,从而降低了系统的整体负担,提高了系统的稳定性和性能。
主动内存规整是系统按照预先设定的规则,定期或者在满足特定条件时主动执行的一种内存管理操作,它就像是工厂里定期设备维护和整理的工人,主动对内存进行梳理和优化,以确保内存始终处于良好的工作状态,为系统的稳定运行提供坚实的保障。
在一些对内存性能和稳定性要求较高的系统中,主动内存规整尤为重要。系统会根据自身的配置和需求,设定特定的时间间隔,例如每隔一定的时间周期(如几分钟、几小时等),就自动触发一次主动内存规整。这种定期执行的方式可以有效地防止内存碎片化问题随着时间的推移而逐渐积累和恶化,就像定期对仓库进行整理,可以保持仓库的整洁和高效运作。
除了定期执行外,系统还会根据一些特定的条件来触发主动内存规整。当系统检测到某个特定的服务或应用程序即将启动,而该服务或应用程序已知对内存的需求较大且对内存连续性要求较高时,系统会在其启动前主动进行内存规整,为其提供一个良好的内存环境,确保其能够顺利启动并高效运行。
当系统处于相对空闲的状态,即 CPU 利用率较低、内存访问压力较小时,也是进行主动内存规整的良好时机。在这种情况下进行内存规整,对系统当前正在运行的其他任务的影响较小,可以充分利用系统的闲置资源来完成内存整理工作,就像在工厂生产任务较少的间隙,对设备进行全面的维护和整理,以提高后续生产的效率。
// 主动规整:用户/系统主动触发内存整理sysfs_compact_memory() {// 手动触发全局内存规整 compact_memory();}主动内存规整的过程与其他内存规整方式类似,系统会对内存中的页面进行重新排列和迁移,将可移动页面集中到一起,合并相邻的空闲页面,减少内存碎片。通过主动内存规整,系统可以保持内存的高效利用,提高内存分配的成功率,减少因内存问题导致的系统性能波动,为用户提供更加稳定和流畅的使用体验。无论是在服务器环境中运行大量关键业务应用,还是在桌面系统中保障用户日常办公和娱乐的顺畅进行,主动内存规整都发挥着不可或缺的作用。
四、内存规整的实战案例
面试题写作模版为了深入探究内存规整的实际效果,我们精心搭建了一个测试环境,力求在接近真实的场景中呈现内存的使用和管理情况。在硬件方面,我们选用了一台性能强劲的服务器,它配备了英特尔至强 E5-2620 v4 处理器,拥有 8 个物理核心,16 个逻辑核心,主频为 2.1GHz,能够高效地处理各种复杂的计算任务。
内存方面,服务器搭载了 64GB 的 DDR4 内存,频率为 2400MHz,这为我们呈现大规模的内存使用提供了充足的空间。存储则采用了一块 512GB 的固态硬盘(SSD),其高速的数据读写能力可以减少因磁盘 I/O 导致的性能瓶颈,确保内存操作的流畅性。
在操作系统层面,我们安装了 64 位的 Ubuntu Server 20.04 LTS,这是一个广泛应用于服务器领域的开源操作系统,拥有稳定的内核和丰富的软件资源,对内存管理和性能优化有着出色的支持。为了更好地监控和分析内存使用情况,我们还安装了一系列工具,如 top、htop、vmstat 和 sysstat 等。
top 和 htop 可以实时显示系统中各个进程的资源使用情况,包括 CPU、内存、磁盘 I/O 等,让我们能够直观地了解内存的分配和使用状态。vmstat 则提供了关于虚拟内存、进程、I/O 等方面的统计信息,帮助我们分析内存的交换、页面错误等情况。sysstat 工具包中的 sar 命令可以收集、报告和保存系统活动信息,通过定期运行 sar,我们可以获取系统在一段时间内的内存使用趋势,为后续的分析提供详细的数据支持。
(1)构建内存碎片化场景:为了复现内存碎片化的产生过程,我们编写了一个专门的 C 程序,该程序通过频繁地分配和释放不同大小的内存块,制造内存碎片。程序的核心逻辑如下:首先,定义一个包含 1000 个内存指针的数组 ptrs,用于存储每次分配的内存块地址。然后通过循环,使用 malloc 函数申请 1000 个大小为 32 字节的内存块,并将地址保存在数组中。这一步复现了程序运行过程中频繁申请小内存块的场景。
#include <stdlib.h>#include <stdio.h>#include <string.h>#define ALLOC_COUNT 1000#define SMALL_SIZE 32#define LARGE_SIZE 5000intmain(){void* ptrs[ALLOC_COUNT];// 批量分配小内存块,占用连续物理空间for (int i = 0; i < ALLOC_COUNT; i++) { ptrs[i] = malloc(SMALL_SIZE);if (ptrs[i] == NULL) { perror("malloc small block failed");return -1; }// 写入数据确保内存真正分配 memset(ptrs[i], 0xAA, SMALL_SIZE); }// 释放间隔内存块,制造物理内存碎片for (int i = 0; i < ALLOC_COUNT; i += 2) { free(ptrs[i]); ptrs[i] = NULL; }// 尝试分配大块连续内存void* large_block = malloc(LARGE_SIZE);if (large_block == NULL) { printf("内存碎片导致大块分配失败\n"); } else { printf("大块内存分配成功\n"); free(large_block); }// 释放剩余内存for (int i = 1; i < ALLOC_COUNT; i += 2) { free(ptrs[i]); }return0;}运行该程序后,我们使用 vmstat 和 /proc/meminfo 等工具对内存状态进行监测。从 vmstat 的输出中,我们可以观察到 si(内存换入)和 so(内存换出)的值逐渐增加,这表明系统由于内存碎片化,不得不频繁地将内存数据交换到磁盘上,以满足内存分配的需求,这严重影响了系统的性能。通过 /proc/meminfo,我们可以看到 MemFree(空闲内存)的数值虽然较大,但 Cached(缓存内存)和 SwapCached(交换缓存)的值也在不断上升,这进一步证明了内存碎片化导致了内存利用率的降低,系统无法有效地利用这些空闲内存。
(2)触发内存规整操作:在构建内存碎片化场景后,我们采用手动触发内存规整的方式,通过修改 /proc/sys/vm/compact_memory 文件来触发内存规整操作。首先,我们打开 /proc/sys/vm/compact_memory 文件,将其值设置为 1,以此来启动内存规整。在触发内存规整之前,我们记录下系统的内存使用情况,包括空闲内存量、已用内存量、内存碎片程度等信息。我们通过 cat /proc/meminfo 命令获取内存信息,并使用自定义的脚本解析这些信息,记录下关键数据。
# 记录触发内存规整前的内存信息before_meminfo=$(cat /proc/meminfo)然后,使用 sysctl 命令将 vm.compact_memory 设置为 1,触发内存规整。
# 触发内存规整sysctl -w vm.compact_memory=1在内存规整过程中,我们可以通过 dmesg 命令查看内核日志,了解内存规整的详细过程。dmesg 会输出内存规整过程中的各种信息,包括哪些页面被迁移、迁移的结果如何等。例如,我们可以看到类似 “Page migration from zone X to zone Y” 的日志信息,这表明内存规整正在将页面从一个内存区域迁移到另一个区域,以合并空闲内存块,减少内存碎片。
# 查看内存规整过程中的内核日志dmesg | grep -i "compaction"内存规整完成后,我们再次记录系统的内存使用情况,以便后续对比分析。
# 记录触发内存规整后的内存信息after_meminfo=$(cat /proc/meminfo)为了更全面地了解内存规整的效果,我们还使用 strace 工具对内存分配和释放的系统调用进行跟踪。strace 可以详细记录程序执行过程中调用的系统函数及其参数和返回值,通过分析这些信息,我们可以了解内存规整对内存分配和释放操作的影响。例如,我们可以观察到在内存规整后,malloc 函数的执行时间是否缩短,内存分配失败的次数是否减少等。
4.3 分析内存规整效果
通过对比内存规整前后的内存使用情况和系统性能指标,我们可以清晰地看到内存规整带来的显著效果。在内存使用方面,内存规整后,空闲内存的连续性得到了明显改善。从 /proc/meminfo 的数据对比中可以发现,内存规整后,MemFree 中的连续空闲内存块大小增加,之前因内存碎片化导致的零散空闲内存块被合并成了更大的连续区域。例如,内存规整前,最大的连续空闲内存块可能只有几百字节,而内存规整后,最大连续空闲内存块的大小增加到了几千字节甚至更大,这大大提高了内存的可用性,使得系统在后续的内存分配中能够更轻松地满足大内存块的请求。
内存碎片程度也大幅降低。我们通过计算内存碎片指数来量化内存碎片化的程度,内存碎片指数的计算公式为:Fragmentation Index = 1 - (LargestFreeBlock / TotalFreeSpace)其中 LargestFreeBlock 表示最大连续空闲块大小,TotalFreeSpace 为总空闲空间。内存规整前,内存碎片指数可能高达 0.8 甚至更高,这意味着内存碎片化非常严重,大量的空闲内存无法被有效利用。而内存规整后,内存碎片指数降低到了 0.2 左右,表明内存的连续性得到了极大的改善,内存的利用率显著提高。
在系统性能方面,内存规整后,应用程序的响应速度明显加快。我们通过运行一个对内存性能要求较高的测试程序来验证这一点,该程序复现了实际应用中频繁的内存读写操作。在内存规整前,测试程序完成一次内存读写操作的平均时间可能需要几十毫秒,而内存规整后,平均时间缩短到了几毫秒,响应速度提升了数倍。这是因为内存规整减少了内存分配失败的次数,应用程序能够更快速地获取所需的内存资源,从而提高了整体的运行效率。
内存规整还降低了系统的 CPU 使用率。在内存碎片化严重时,系统需要花费大量的 CPU 时间来处理内存分配和交换等操作,导致 CPU 使用率居高不下。而内存规整后,内存管理变得更加高效,CPU 可以将更多的时间用于处理实际的应用任务,使得 CPU 使用率从之前的 80% 左右降低到了 50% 左右,系统的整体性能得到了显著提升。
五、内存规整的优化策略
面试题写作模版在内存管理的复杂体系中,内存参数的合理配置就像是为一场精密的交响乐设定精准的演奏节奏,对内存规整和系统性能的提升起着至关重要的作用。与内存规整密切相关的系统参数众多,其中内存水位线和回收策略是两个关键的调控因素。
内存水位线是内存管理中的一个重要概念,它就像水库的水位标识一样,用于衡量内存的使用状态和可分配程度。在 Linux 系统中,内存水位线通常分为高水位线(WMARK_HIGH)、低水位线(WMARK_LOW)和最低水位线(WMARK_MIN)。当内存的空闲量高于高水位线时,内存处于相对宽松的状态,系统可以较为轻松地满足各种内存分配请求;当空闲内存量下降到低水位线附近时,系统开始警觉,需要更加谨慎地管理内存,以避免内存不足的情况发生;而当空闲内存量逼近最低水位线时,系统进入紧急状态,必须采取强力措施来回收内存和进行内存规整,以确保系统的正常运行。
合理调整内存水位线的值可以根据系统的实际需求和负载情况,优化内存的分配和使用效率。例如,在一个对内存稳定性要求较高的服务器系统中,如果将高水位线设置得相对较高,那么系统在大部分时间内都能保持较为充足的空闲内存,从而减少因内存紧张导致的性能波动;相反,在一个内存资源有限但任务相对较轻的嵌入式系统中,可以适当降低水位线的值,以提高内存的利用率。
回收策略则决定了系统在内存不足时如何回收内存页面。常见的内存回收策略包括最近最少使用(LRU,Least Recently Used)算法及其变种。LRU 算法的核心思想是认为最近最少被访问的页面在未来一段时间内也最有可能不会被访问,因此在内存不足时优先回收这些页面。这种策略在大多数情况下都能有效地平衡内存的使用和回收,确保系统能够及时释放不再使用的内存资源。但是,在某些特殊的应用场景中,LRU 算法可能并不适用。
例如,在一些实时性要求极高的系统中,可能需要采用其他更具针对性的回收策略,如基于时间戳的回收策略,该策略不仅考虑页面的访问频率,还结合页面的创建时间戳,优先回收那些创建时间较早且长时间未被访问的页面,以确保关键的实时任务始终有足够的内存可用。在配置内存回收策略时,还需要考虑与内存规整的协同工作。例如,在回收内存页面时,可以同时触发内存规整操作,将回收后的空闲页面及时合并和整理,提高内存的连续性和可用性。
内存分配算法就像是一个城市的土地规划者,决定着内存资源在各个程序和进程之间的分配方式,不同的内存分配算法对内存碎片化的影响犹如不同的城市规划方案对城市空间利用的影响一样,有着显著的差异。选择或优化内存分配算法是减少内存碎片、提高内存利用率的关键一环。
在常见的内存分配算法中,首次适应(First Fit)算法是一种较为简单直观的分配策略。当有内存分配请求时,它会从内存空闲链表的头部开始遍历,找到第一个能够满足请求大小的空闲内存块,然后将其分配给请求者。这种算法的优点是实现简单,分配速度相对较快,因为它不需要对整个空闲链表进行全面搜索。但是,它也容易导致内存碎片化,尤其是在频繁分配和释放不同大小内存块的场景下。由于每次都优先使用链表头部的空闲块,随着时间的推移,链表头部会出现许多零散的小空闲块,这些小空闲块很难再被利用,从而形成外部碎片。
最佳适应(Best Fit)算法则试图解决首次适应算法的这一问题。它会遍历整个空闲链表,找到大小最接近请求大小的空闲内存块进行分配。这样做的好处是可以尽量减少分配后剩余的空闲内存块大小,从而降低外部碎片的产生。但是,最佳适应算法的缺点也很明显,它需要对整个空闲链表进行遍历,这会增加内存分配的时间开销,降低分配效率。而且,由于每次都选择最小的合适空闲块,可能会导致内存中残留大量难以利用的微小空闲块,在某些情况下反而加剧了内存碎片化。
为了减少内存碎片化,一些更高级的内存分配算法被提出,如伙伴系统(Buddy System)算法。伙伴系统算法将内存划分为大小为 2 的幂次方的块,例如 4KB、8KB、16KB 等。当有内存分配请求时,它会根据请求大小找到最接近的 2 的幂次方大小的空闲块进行分配。如果空闲块大小大于请求大小,它会将空闲块分割成两个相等的 “伙伴” 块,其中一个用于分配,另一个则加入空闲链表。当一个内存块被释放时,系统会检查其伙伴块是否也为空闲状态,如果是,则将两个伙伴块合并成一个更大的块。
这种机制有效地减少了内存碎片化,因为它始终以 2 的幂次方大小的块为单位进行分配和合并,避免了产生大量不规则的小空闲块。伙伴系统算法在操作系统内核的内存管理中得到了广泛应用,尤其是在对内存连续性要求较高的场景下,如内核空间的内存分配。
在实际应用中,根据系统的特点和需求选择合适的内存分配算法至关重要。对于那些内存分配请求大小相对固定、对分配速度要求较高的系统,可以考虑采用简单高效的首次适应算法,并结合一些内存缓存机制来减少频繁分配和释放带来的碎片问题;而对于内存分配请求大小差异较大、对内存碎片化较为敏感的系统,则可以选择更复杂但更有效的伙伴系统算法或其他优化算法。
还可以对现有的内存分配算法进行优化,例如在首次适应算法的基础上,增加对空闲链表的定期整理机制,将相邻的空闲块合并,减少外部碎片;或者在最佳适应算法中,引入缓存机制,记录最近使用的空闲块信息,减少遍历整个链表的次数,提高分配效率。
定期执行内存规整就像是定期对杂乱的房间进行大扫除,是维持内存良好状态、保障系统高效稳定运行的重要手段。随着系统的持续运行,内存中的数据不断地被分配、释放和重新分配,就像房间里的物品被频繁地摆放和挪动,不可避免地会导致内存碎片化问题逐渐积累和恶化。如果不及时进行处理,内存碎片会越来越多,严重影响内存的利用率和系统的性能。
定期执行内存规整可以有效地解决这一问题。通过按照一定的时间间隔,如每隔几个小时或每天凌晨系统负载较低时,主动触发内存规整操作,可以及时清理内存中的碎片,将零散的空闲内存块合并成连续的大块内存,为系统后续的内存分配提供更充足的空间。定期内存规整还可以减少因内存碎片化导致的内存分配失败次数,提高系统的稳定性。在服务器环境中,许多关键业务应用需要长时间稳定运行,如果内存碎片化问题得不到及时解决,可能会导致这些应用因内存不足而崩溃,造成严重的业务损失。通过定期执行内存规整,可以避免这种情况的发生,确保服务器能够持续可靠地为用户提供服务。
合理设置内存规整的执行周期是至关重要的。如果执行周期过短,频繁地进行内存规整操作会消耗大量的系统资源,包括 CPU 时间和内存带宽,从而影响系统当前正在运行的其他任务的性能。因为内存规整过程中需要进行数据的迁移和页表的更新等复杂操作,这些操作都会占用一定的系统资源。相反,如果执行周期过长,内存碎片化问题可能会在这段时间内积累到较为严重的程度,导致内存利用率大幅下降,系统性能受到显著影响。
设置执行周期时,需要综合考虑系统的负载情况、内存使用模式以及业务需求等因素。对于那些内存使用频繁、负载变化较大的系统,可以适当缩短执行周期,以保持内存的高效利用;而对于内存使用相对稳定、负载较低的系统,则可以适当延长执行周期,减少不必要的系统开销。此外,还可以根据系统的实时性能指标,如内存碎片率、内存分配失败次数等,动态调整内存规整的执行周期,以实现内存管理的最优效果。
六、附录:内存规整面试问题解析
面试题写作模版在实际的系统运行过程中,内存规整并不是随意启动的,它有着明确的触发时机:
内存规整算法就像是一场精心策划的 “内存搬家” 行动,以解决内存碎片化问题。其核心原理是通过迁移内存中的页面,将分散的空闲内存块合并,使内存布局更加紧凑和连续,从而提升内存的使用效率 。
内存规整和内存分配策略就像一对紧密合作的伙伴,共同影响着系统内存的使用效率和性能。
不同操作系统就像各具特色的工匠,在内存规整的实现上各有千秋:
重点必读:当面试官抛出内存规整相关问题时,不要急于回答,而是要先深入思考问题的本质。比如,问 “内存规整的触发条件有哪些”,这背后考查的是你对内存管理机制中内存规整启动时机的理解,以及对系统内存分配和回收过程的熟悉程度 。你需要明白,面试官期望通过这个问题,了解你是否清楚在何种情况下系统会启动内存规整来优化内存布局,解决内存碎片化问题。只有理解了问题的本质,才能有的放矢地组织答案,避免答非所问。
end
如果这篇文章对你有所启发,欢迎点赞、在看,转发三连。星标⭐账号,还可以第一时间收到推送,感谢你的收看,我们下期再见~
往期干货推荐