Ubuntu 内存占用解析

内存主要用来存储操作系统和应用程序的指令,数据和缓存等,内存管理也是操作系统最核心的功能之一。

我的新书《LangChain编程从入门到实践》 已经开售!推荐正在学习AI应用开发的朋友购买阅读!
LangChain编程从入门到实践

内存的工作原理

内存映射

进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。虽然每个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是相同的物理内存。进程切换到内核态后,可以很方便地访问内核空间内存。
每个进程都有自己的地址空间,所有进程的虚拟内存加起来,要比实际的物理内存大得多。所以并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。
内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系。
页表实际上存储在 CPU 的内存管理单元 MMU中,这样,正常情况下,处理器就可以直接通过硬件,找出要访问的内存。
而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。

虚拟内存空间分布(32bit)

用户空间内存,从低到高分别是五种不同的内存段。
memory.png

  1. 只读段,包括代码和常量等。
  2. 数据段,包括全局变量等。
  3. 堆,包括动态分配的内存,从低地址开始向上增长。
  4. 文件映射段,包括动态库、共享内存等,从高地址开始向下增长。
  5. 栈,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。

在这五个内存段中,堆和文件映射段的内存是动态分配的。使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。

内存分配与回收

对小块内存(小于128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存,这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。

  • 优点:减少缺页异常的发生,提高内存访问效率
  • 缺点:频繁的分配和释放可能会造成内存碎片

而大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。

  • 优点:释放时,直接归还系统
  • 缺点:导致缺页异常

内存回收方式

  • 基于 LRU(Least Recently Used)算法,回收缓存(也称文件页);
  • 基于 Swap 机制回收不常访问的内存(也称匿名页),即换出到磁盘;
  • 基于 OOM(Out of Memory)机制,杀掉占用大量内存的进程。

注意:第三种方式提到的 OOM(Out of Memory),其实是内核的一种保护机制。它监控进程的内存使用情况,并且使用 oom_score 为每个进程的内存使用情况进行评分:一个进程消耗的内存越大,oom_score 就越大;一个进程运行占用的 CPU 越多,oom_score 就越小。这样,进程的 oom_score 越大,代表消耗的内存越多,也就越容易被 OOM 杀死,从而可以更好保护系统。当然也可以通过/proc文件系统,手动设置进程的 oom_adj ,从而调整进程的 oom_score。oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死;数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。
例如把 sshd 进程的 oom_adj 调小为 -16,sshd 进程就不容易被 OOM 杀死echo -16 > /proc/$(pidof sshd)/oom_adj

分析工具

free

1
2
3
4
5
$ free
total used free shared buff/cache available
Mem: 1744140 744280 171324 143580 828536 591124
Swap: 9764860 125008 9639852

total 是总内存大小;
used 是已使用内存的大小,包含了共享内存;
free 是未使用内存的大小;
shared 是共享内存的大小;
buff/cache 是缓冲区和缓存的大小;
available 是新进程可用内存的大小。

available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。不过,并不是所有缓存都可以回收,因为有些缓存可能正在使用中。

buff/cache
1
2
buffers Memory used by kernel buffers (Buffers in /proc/meminfo)
cache Memory used by the page cache and slabs (Cached and Slab in /proc/meminfo)

Buffers 是内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值。
Cache 是内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached与 SReclaimable 之和。

1
2
3
4
5
6
7
8
9
10
11

Buffers %lu
Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB
or so).
Cached %lu
In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
...
SReclaimable %lu (since Linux 2.6.19)
Part of Slab, that might be reclaimed, such as caches.
SUnreclaim %lu (since Linux 2.6.19)
Part of Slab, that cannot be reclaimed on memory pressure.

Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样内核就可以把分散的写集中起来,优化磁盘的写入。(Buffer 也会缓存从磁盘读取的数据)
Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。(实际上,Cache 也会缓存写文件时的数据)
SReclaimable 是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。

用下面的命令可以释放 Cache Memory(释放前最好sync一下,防止丢失数据):

1
2
3
4
5
6
To free pagecache:
echo 1 > /proc/sys/vm/drop_caches
To free dentries and inodes:
echo 2 > /proc/sys/vm/drop_caches
To free pagecache, dentries and inodes:
echo 3 > /proc/sys/vm/drop_caches

缓存命中率

cachestat 提供了整个操作系统缓存的读写命中情况;cachetop 提供了每个进程的缓存命中情况。
这两个工具都是 bcc 软件包的一部分,它们基于 Linux 内核的 eBPF(extended Berkeley PacketFilters)机制,来跟踪内核中管理的缓存,并输出缓存的使用和命中情况

  • 安装配置过程(bcc 提供的所有工具就都安装到/usr/share/bcc/tools这个目录中了,还需手动配置到系统的的 PATH 路径中)

    1
    2
    3
    4
    5
    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
    echo "deb https://repo.iovisor.org/apt/xenial xenial main" | sudo tee /etc/apt/sources.list
    sudo apt update
    sudo apt install -y bcc-tools libbcc-examples linux-headers-$(uname -r)
    export PATH=$PATH:/usr/share/bcc/tools
  • 使用方法如下:

    1
    2
    3
    4
    5
    6
    # 以 1 秒的时间间隔,输出了 3 组缓存统计数据
    $ cachestat 1 3
    HITS MISSES DIRTIES HITRATIO BUFFERS_MB CACHED_MB
    4331 0 0 100.00% 165 310
    4361 0 0 100.00% 165 310
    4349 0 0 100.00% 165 310

    TOTAL 是总的 I/O 次数
    MISSES 是缓存未命中次数
    HITS 是缓存命中次数
    DIRTIES 是新增到缓存中的脏页数

1
2
3
4
5
6
$ cachetop
11:10:34 Buffers MB: 166 / Cached MB: 312 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
10177 root sleep 9 0 0 100.0% 0.0%
10057 root cachetop 36 0 0 100.0% 0.0%

top

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ top
top - 21:47:47 up 6:34, 2 users, load average: 0.00, 0.00, 0.00
Tasks: 214 total, 1 running, 213 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.2 sy, 0.0 ni, 99.7 id, 0.1 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1744140 total, 170300 free, 745204 used, 828636 buff/cache
KiB Swap: 9764860 total, 9639852 free, 125008 used. 590184 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2190 morso 20 0 1406660 148976 18608 S 0.0 8.5 0:10.41 gnome-software
2239 morso 20 0 1257852 91316 49932 S 0.7 5.2 2:22.55 compiz
2293 root 20 0 659912 53492 4624 S 0.0 3.1 0:02.90 fwupd
1173 root 10 -10 396020 52428 9356 S 0.7 3.0 1:06.84 ovs-vswitchd
2247 morso 20 0 860816 42812 4480 S 0.0 2.5 0:00.22 evolution-calen
1095 root 20 0 374152 41272 27840 S 0.3 2.4 0:30.97 Xorg
2339 morso 20 0 1072680 41036 4168 S 0.0 2.4 0:01.96 evolution-calen
...

VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
%MEM 是进程使用物理内存占系统总内存的百分比。
注意:

  • 虚拟内存通常并不会全部分配物理内存,所以上面每个进程的虚拟内存都比常驻内存大得多。
  • 共享内存 SHR 并不一定是共享的,比如程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然 SHR 也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的 SHR 直接相加得出结果。

Ubuntu 内存占用解析

https://liduos.com/linux-memory.html

作者

莫尔索

发布于

2020-08-14

更新于

2024-05-19

许可协议

评论