当前位置: 首页 > java >正文

linux内存管理

数据结构

物理内存(Physical Memory)
  • 定义:物理内存是计算机硬件实际存在的内存芯片(如DRAM),直接由CPU通过物理地址访问。
  • 特点:
    • 容量有限,地址空间连续(从0到最大物理地址)。
    • 由操作系统直接管理,分配方式包括连续分配(如分段)或非连续分配(如分页)。
    • 进程无法直接使用物理地址(避免安全问题和管理复杂度)。
虚拟内存(Virtual Memory)
  • 定义:虚拟内存是操作系统为每个进程提供的抽象内存空间,通过地址转换机制映射到物理内存或磁盘(如交换空间)。
  • 特点:
    • 每个进程拥有独立的虚拟地址空间(如32位系统为4GB),隔离且安全。
    • 支持分页(Page)或分段(Segment)机制,将虚拟地址划分为固定/可变大小的单元。
    • 允许部分数据驻留在磁盘(按需加载),实现内存扩展和多任务隔离。

虚拟内存与物理内存的关联
(1)页表(Page Table)
  • 作用:存储虚拟页到物理页帧的映射关系,由操作系统维护。
  • 结构:
    • 多级页表(如x86的4级页表)节省空间,支持稀疏地址空间。
    • 页表项(PTE)包含物理页帧号、权限位(读/写/执行)、有效位(是否在内存)等。
  • 地址转换:
    • CPU访问虚拟地址 → MMU通过页表查询物理地址 → 访问内存。
    • 若页不在内存(缺页异常),触发操作系统从磁盘加载。
(2)TLB(Translation Lookaside Buffer)
  • 作用:缓存频繁使用的页表项,加速地址转换。
  • 特点:
    • 硬件实现的高速缓存(通常为64-128条目)。
    • 命中时直接获取物理地址,未命中时查页表(可能触发TLB重填)。
(3)MMU(Memory Management Unit)
  • 作用:硬件单元(集成在CPU中)负责虚拟到物理地址的转换。
  • 工作流程:
    1. CPU发出虚拟地址 → MMU优先查询TLB。
    2. TLB未命中 → MMU自动遍历页表(多级页表需多次访存)。
    3. 转换成功后更新TLB,并访问物理内存。
    4. 若页表项无效(缺页)或权限不足,触发异常由操作系统处理。

内存分配

物理内存

Buddy System

作用:管理大块连续物理内存(通常以页为单位)

数据结构 : 空闲链表数组:每个数组元素对应一种块大小(如 1, 2, 4, 8 页),存储该大小的空闲块链表。

算法步骤

  1. 分配
    找到满足大小的最小块(2n≥请求大小)。
    若对应链表为空,递归向上分割更大的块。
  2. 释放
    检查伙伴块是否空闲,若空闲则合并为更大的块。

时间复杂度:分配和释放均为 O(1)。

Slab

板坯(Slab), 将内存划分为固定大小的“板块”,用于高效管理对象缓存。

作用:高效管理内核对象(如 task_struct)的内存分配。

数据结构

  • Slab Cache:每个缓存对应一种对象类型,包含多个 Slab。
  • Slab:一个连续的内存块,包含多个对象和元数据(如空闲对象链表)。

算法步骤

  1. 分配:从 Slab 的空闲对象链表中取出一个对象。
  2. 释放:将对象重新加入空闲链表,供后续重用。

优势:避免频繁初始化对象,减少内存碎片。


对比

特性伙伴系统(Buddy System)SLAB分配器
主要用途管理大块连续物理内存(通常以页为单位)管理小对象内存(如内核数据结构的频繁分配/释放)
分配粒度页(如4KB)为基数,按2的幂次方分配(如4KB、8KB、16KB)对象(如几十字节到几KB)为单位,精确匹配请求大小
典型场景用户进程的匿名内存、文件缓存、大块DMA缓冲区内核数据结构(如task_struct、inode、sk_buff等)
碎片问题外部碎片(通过合并相邻空闲块缓解)内部碎片(因对象固定大小可能浪费部分空间)

虚拟内存

分为用户态和内核态,在32位上分别为3G:1G

kmalloc

  • 本质:

    • 内核提供的函数,用于在内核态动态分配连续的虚拟内存(映射到物理内存)。
    • 底层依赖伙伴系统(大块)或SLAB/SLUB(小块)。
  • 特点:

    • 物理连续:默认返回物理地址连续的内存(适用于DMA操作)。

    • 大小限制:通常限制为单个页框的倍数(如4KB的整数倍)。

      void *kmalloc(size_t size, gfp_t flags);  // 如 GFP_KERNEL(可睡眠)、GFP_ATOMIC(原子上下文)
      

malloc

  • 本质:

    • C库(如glibc)提供的函数,分配用户态虚拟内存,可能不连续物理内存。
    • 底层通过brkmmap系统调用向内核申请内存。
  • 特点:

    • 虚拟连续:返回的地址在用户态虚拟空间连续,物理内存可能不连续。

    • 灵活大小:可分配任意大小(由C库的内存池管理)。

      void *malloc(size_t size);  // 如 malloc(1024)
      

对比

维度kmalloc(内核态)malloc(用户态)
运行层级内核态(Ring 0)用户态(Ring 3)
底层实现基于 SLAB分配器(小对象)或 伙伴系统(大块)基于 ptmalloc/jemalloc 等库,底层通过 brk/mmap 系统调用向内核申请内存
内存来源直接管理物理内存(可能涉及DMA、高端内存等区域)操作虚拟内存,由内核分配物理页并映射到进程地址空间
分配粒度通常按 字节对齐(如GFP_KERNEL标志控制)页(4KB)或更小单位(依赖库实现)
失败行为可能直接panic(如GFP_ATOMIC失败)返回NULL,进程可处理
并发安全需显式处理锁/原子上下文(如GFP_ATOMIC由库内部实现线程安全(如ptmalloc的arena机制)
使用场景内核数据结构(如sk_bufftask_struct用户进程的堆内存(如数组、对象)

内存回收

基于内存交换的速率

回收策略

页面置换

作用:选择不常用的内存页写入交换空间(Swap)。

数据结构

  • 活跃链表(Active List):存放最近被访问的页。
  • 非活跃链表(Inactive List):存放候选回收的页。

算法步骤

  1. 新访问的页加入活跃链表尾部。
  2. 定期将活跃链表中较旧的页移动到非活跃链表。
  3. 优先回收非活跃链表中的页。

优化:通过“二次机会”策略避免频繁置换常用页。

常见

算法核心思想优点缺点适用场景
FIFO置换最早进入内存的页面实现简单Belady异常(缺页率可能随帧增加而升高)早期系统,教学示例
LRU置换最久未使用的页面符合局部性原理,缺页率低实现复杂(需硬件支持或近似算法)通用场景(理论最优)
Clock近似LRU,使用访问位轮询开销低于LRU可能误判活跃页面实际系统(如Linux)
LFU置换使用频率最低的页面适合长期热点数据难以适应访问模式变化数据库缓存
Optimal置换未来最长时间不使用的页面(理论)缺页率最低(理想情况)无法实际实现(需预知未来)

平台

平台页面置换算法核心机制特点
Windows工作集 + 时钟算法(WS-Clock)- 基于工作集模型(活跃页面优先保留) - 近似LRU的时钟算法(二次机会置换)- 动态调整工作集大小 - 支持优先级内存分配(如前台进程优先)
LinuxClock(二次机会) + 交换优先级- 全局时钟扫描(kswapd内核线程) - 支持多种置换策略(/proc/sys/vm/swappiness调节)- 支持NUMA优化 - 支持压缩内存(zswap)替代部分磁盘交换
macOS混合策略(FIFO + 压缩)- FIFO为主,结合内存压缩技术 - 优先压缩非活跃内存(Jetsam机制回收内存)- 对iOS/macOS统一优化 - 激进

内存压缩

作用:压缩内存页以减少物理内存占用。

数据结构

  • 压缩池:存储压缩后的内存页。

算法

  1. 将候选页压缩后存入压缩池。
  2. 当需要访问时,解压缩并恢复为原始页。

优势:延迟触发交换操作,提高性能。

空间交换

作用:将不常用的内存页写入磁盘,释放物理内存。

数据结构

  • 交换分区(Swap Partition):磁盘上的一个独立分区,专门用于交换空间。
  • 交换文件(Swap File):磁盘上的一个文件,作为交换空间使用。
  • 交换缓存(Swap Cache)记录已写入交换空间的内存页的状态(如脏页、访问时间等)。
  • 页表项(PTE): 记录内存页是否在交换空间中,以及交换空间中的位置。

算法

  1. 页面选择:使用改进的 LRU 算法选择不常用的内存页作为候选。
  2. 写入交换空间:将选中的内存页写入交换分区或交换文件。更新页表项,标记该页在交换空间中。
  3. 读回内存:当进程访问已交换的页时,触发缺页异常(Page Fault)。从交换空间中读回该页,更新页表项,标记为在物理内存中。

优势:扩展内存容量,防止系统因内存不足而崩溃。


OOM Killer

作用:在内存严重不足时终止进程。

算法

  1. 计算进程的 oom_score(基于内存占用、优先级等)。
  2. 选择得分最高的进程终止。

策略:牺牲少数进程,防止系统崩溃。


对比

操作系统通常按以下顺序逐级触发更激进的回收机制,形成「内存回收漏斗」:

回收策略触发条件开销影响范围
页面置换(LRU)内存水位低于low阈值(由/proc/sys/vm/lowmem_reserve_ratio定义)低(仅扫描)少数不活跃进程
内存压缩置换后仍无法满足需求,或/proc/sys/vm/compaction_proactiveness配置主动压缩中(CPU计算)可压缩的匿名页/文件页
空间交换压缩后内存仍不足,或/proc/sys/vm/swappiness设置倾向交换(默认60,0禁用交换)高(磁盘I/O)匿名页(堆、栈等)
OOM Killer交换空间耗尽且/proc/sys/vm/panic_on_oom=0(不直接panic)致命(杀进程)占用内存最多的进程

压力指标

内存水位

  • 定义:内核为每个内存区域(Zone)预设三个阈值,通过

    /proc/zoneinfo
    

    查看:

    grep -A10 "Node 0" /proc/zoneinfo | grep -E "min|low|high"
    
    • high:理想水位,无需回收。
    • low:后台回收(kswapd)启动阈值。
    • min:直接回收(同步回收)阈值,可能阻塞进程。
  • 触发策略:

    • 可用内存 < low → 启动页面置换(异步回收)。
    • 可用内存 < min → 强制同步回收(可能触发压缩/交换)。

页面活跃度

  • 指标来源:
    • /proc/meminfo中的Active/Inactive内存统计。
    • 内核维护的LRU链表(活跃 vs 非活跃)。
  • 决策逻辑:
    • 非活跃页面比例高 → 优先回收非活跃页(低开销)。
    • 活跃页面占比过高 → 需加大扫描压力或触发压缩。

交换倾向

  • 指标/proc/sys/vm/swappiness(默认60,范围0-100)。
  • 作用:
    • 值越高,越倾向于交换匿名页(如进程堆内存)。
    • 值越低,越倾向于回收文件页(如缓存)。
  • 特殊场景:
    • swappiness=0:除非极端情况,否则禁用交换(适合数据库)。

缺页率

  • 监控命令:

    sar -B 1  # 查看pgpgin/pgpgout(页换入/换出)
    
  • 高压标志:

    • **主要缺页(Major Faults)**激增 → 表明频繁从磁盘加载页,需加速回收。

策略选择

页面置换

  • 触发条件:
    • 内存水位 < low 非活跃页充足。
  • 策略特点:
    • 低开销,仅扫描LRU链表并标记可回收页。
    • 优先回收干净文件页(无需I/O)。

内存压缩

  • 触发条件:
    • 非活跃文件页不足 swappiness较低。
  • 依赖配置:
    • 内核启用CONFIG_ZSWAP,通过/sys/kernel/mm/zswap调整压缩比。

空间交换

  • 触发条件:
    • 内存水位 < min 文件页回收无效。
    • swappiness > 0时,匿名页成为候选。
  • 关键限制:
    • 交换分区/文件未用尽(通过free -h查看Swap行)。

OOM Killer

  • 终极条件:
    • 可用内存 ≈ 0 交换空间耗尽 回收失败。
  • 选择牺牲者:
    • 根据oom_score(综合内存占用、进程优先级等)。

实战

堆栈

代码

#include <iostream>
#include <cstdlib>
#include <vector>// 函数展示栈分配的变量地址
void stackAllocationDemo() {int a = 1;int b = 2;int c = 3;std::cout << "Stack allocated variables addresses:\n";std::cout << "&a: " << &a << " (value: " << a << ")\n";std::cout << "&b: " << &b << " (value: " << b << ")\n";std::cout << "&c: " << &c << " (value: " << c << ")\n";// 计算地址差值std::cout << "Difference between &a and &b: "<< reinterpret_cast<char*>(&b) - reinterpret_cast<char*>(&a) << " bytes\n";std::cout << "Difference between &b and &c: "<< reinterpret_cast<char*>(&c) - reinterpret_cast<char*>(&b) << " bytes\n";
}// 函数展示堆分配的变量地址
void heapAllocationDemo() {// 分配不同大小的内存块void* p1 = malloc(16);void* p2 = malloc(32);void* p3 = malloc(64);void* p4 = malloc(128);void* p5 = malloc(256);std::cout << "\nHeap allocated memory addresses:\n";std::cout << "p1 (16 bytes): " << p1 << "\n";std::cout << "p2 (32 bytes): " << p2 << "\n";std::cout << "p3 (64 bytes): " << p3 << "\n";std::cout << "p4 (128 bytes): " << p4 << "\n";std::cout << "p5 (256 bytes): " << p5 << "\n";// 计算地址差值std::cout << "\nAddress differences:\n";std::cout << "p2 - p1: " << (static_cast<char*>(p2) - static_cast<char*>(p1)) << " bytes\n";std::cout << "p3 - p2: " << (static_cast<char*>(p3) - static_cast<char*>(p2)) << " bytes\n";std::cout << "p4 - p3: " << (static_cast<char*>(p4) - static_cast<char*>(p3)) << " bytes\n";std::cout << "p5 - p4: " << (static_cast<char*>(p5) - static_cast<char*>(p4)) << " bytes\n";// 释放内存free(p1);free(p2);free(p3);free(p4);free(p5);
}int main() {stackAllocationDemo();heapAllocationDemo();return 0;
}

结果

Stack allocated variables addresses:
&a: 0x7fffcc3701dc (value: 1)
&b: 0x7fffcc3701e0 (value: 2)
&c: 0x7fffcc3701e4 (value: 3)
Difference between &a and &b: 4 bytes
Difference between &b and &c: 4 bytesHeap allocated memory addresses:
p1 (16 bytes): 0x5556a9d942c0
p2 (32 bytes): 0x5556a9d942e0
p3 (64 bytes): 0x5556a9d94310
p4 (128 bytes): 0x5556a9d94360
p5 (256 bytes): 0x5556a9d943f0//实际的地址差值比实际分配大小多16bytes,实际上是存放的元数据,表示当前内存块的大小
Address differences:
p2 - p1: 32 bytes
p3 - p2: 48 bytes
p4 - p3: 80 bytes
p5 - p4: 144 bytes
http://www.xdnf.cn/news/5607.html

相关文章:

  • C盘扩容方法:如何扩展不相邻的分区?
  • 8天Python从入门到精通【itheima】-1~5
  • Baumer工业相机堡盟工业相机在使用光源时如何选择蓝光还是红光
  • 制作一款打飞机游戏43:行为编辑
  • dfs算法第二次加训之普及/提高- ,详解 上
  • GPT系列:自然语言处理的演进与多模态的探索
  • day012-软件包管理专题
  • ms-swift 代码推理数据集
  • iOS即时通信的技术要点
  • 扩展:React 项目执行 yarn eject 后的 package.json 变化详解及参数解析
  • 25、Tailwind:魔法速记术——React 19 样式新思路
  • LeetCode 热题 100_只出现一次的数字(96_136_简单_C++)(哈希表;哈希集合;排序+遍历;位运算)
  • STC15W408AS计数器
  • 【C++11】异常
  • 配置集群-日志聚集操作
  • 三大告警方案解析:从日志监控到流处理的演进之路
  • 长度最小的子数组(leetcode)
  • C++ 与 Go、Rust、C#:基于实践场景的语言特性对比
  • 风车OVF镜像:解放AI开发限制的Ubuntu精简系统
  • 【vue】全局组件及组件模块抽离
  • Maven 项目中将本地依赖库打包到最终的 JAR 中
  • 如何使用主机名在 CMD 中查找 IP 地址?
  • leetcode 18. 四数之和
  • 力扣 旋转图像
  • 横向移动(上)
  • zorin系统详解
  • 牛客周赛 Round 92(再现京津冀蓝桥杯???)
  • C++23 中的 views::stride:让范围操作更灵活
  • 「华为」人形机器人赛道投资首秀!
  • STM32核心机制解析:重映射、时间片与系统定时器实战——从理论到呼吸灯开发