内存

查看系统内存使用情况

使用 free , 单位为 B

1
2
3
4
root@imwl01:~# free
total used free shared buff/cache available
Mem: 4001696 514836 2834488 1572 652372 3230228
Swap: 0 0 0

  1. total : 总内存大小
  2. used : 已使用内存的大小,包含了共享内存
  3. free : 未使用内存的大小
  4. shared : 共享内存的大小 tmpfs ,一种特殊的缓存
  5. buff/cache : 缓存和缓冲区的大小 Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中
  6. available : 新进程可用内存的大小,包含 未使用 + 可回收

读写普通文件时,I/O 请求会首先经过文件系统,然后由文件系统负责,来与磁盘进行交互 cache。而在读写块设备文件时,会跳过文件系统,直接与磁盘交互,也就是所谓的”裸 I/O” buff

使用 top ,按 M 按内存排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@imwl01:~# top
top - 16:42:46 up 11 min, 1 user, load average: 0.05, 0.03, 0.00
Tasks: 241 total, 1 running, 240 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.0 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 3907.9 total, 2711.3 free, 512.0 used, 684.6 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 3145.6 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2231 root 20 0 822836 84952 34176 S 0.3 2.1 0:04.40 node
1014 root 20 0 1383888 78828 51756 S 0.0 2.0 0:00.47 dockerd
2026 root 20 0 926520 62968 31896 S 0.3 1.6 0:02.57 node
1443 root 20 0 164268 53020 11428 S 0.0 1.3 0:00.48 jupyter-noteboo
2238 root 20 0 917196 52236 29564 S 0.3 1.3 0:02.18 node
938 root 20 0 1492628 42612 28680 S 0.3 1.1 0:01.27 containerd
2197 root 20 0 653708 39024 29936 S 0.0 1.0 0:00.18 node
926 root 20 0 1094772 38136 18964 S 0.0 1.0 0:01.51 snapd
524 root 19 -1 67148 22564 21424 S 0.0 0.6 0:00.28 systemd-journal
974 root 20 0 107908 20844 13176 S 0.0 0.5 0:00.08 unattended-upgr
749 root rt 0 345880 18264 8300 S 0.0 0.5 0:00.21 multipathd
920 root 20 0 29076 18132 10408 S 0.0 0.5 0:00.06 networkd-dispat
............................
  1. VIRT : 进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
  2. RES : 常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
  3. SHR : 共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
  4. %MEM : 进程使用物理内存占系统总内存的百分比。

查看所有进程使用的内存总量
Pss: 把共享内存平分到各个进程后,再加上进程本身的非共享内存大小的和

1
2
# 使用 grep 查找 Pss 指标后,再用 awk 计算累加值
$ grep Pss /proc/[1-9]*/smaps | awk '{total+=$2}; END {printf "%d kB\n", total }'

虚拟内存

内存是稀缺的,随着应用使用内存也在膨胀。当程序越来复杂,进程对内存的需求会越来越大。

虚拟化技术是为了解决内存不够用的问题

虚拟化技术中,操作系统设计了虚拟内存(理论上可以无限大的空间),受限于 CPU 的处理能力,通常 64bit CPU,就是 2**64 个地址

虚拟化技术中,应用使用的是虚拟内存,操作系统管理虚拟内存和真实内存之间的映射。操作系统将虚拟内存分成整齐小块,每个小块称为一个页(Page)。之所以这样做,原因主要有以下两个方面。

一方面应用使用内存是以页为单位,整齐的页能够避免内存碎片问题。

另一方面,每个应用都有高频使用的数据和低频使用的数据。这样做,操作系统就不必从应用角度去思考哪个进程是高频的,仅需思考哪些页被高频使用、哪些页被低频使用。如果是低频使用,就将它们保存到硬盘上;如果是高频使用,就让它们保留在真实内存中。

如果一个应用需要非常大的内存,应用申请的是虚拟内存中的很多个页,真实内存不一定需要够用

大页 : 比普通页(4KB)更大的内存块,常见的大小有 2MB 和 1GB。大页通常用在使用大量内存的进程上,比如 Oracle、DPDK 等
内存页

内存映射

  1. 物理内存 : 只有内核才可以直接访问
  2. 虚拟内存 : Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的

内存映射 : 其实就是将虚拟内存地址映射到物理内存地址

内存映射

虚拟内存空间分布

虚拟内存空间分布

内存分配与回收

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

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

回收

  1. 回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面;

  2. 回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中

  3. 杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程。

内存泄漏

  1. 只读段,包括程序的代码和常量,由于是只读的,不会再去分配新的内存,所以也不会产生内存泄漏。

  2. 数据段,包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。

堆,栈, 内存映射段,包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。所以,如果程序在分配后忘了回收,就会导致泄漏问题

Swap

Swap 技术允许一部分进程使用内存,不使用内存的进程数据先保存在磁盘上。注意,这里提到的数据,是完整的进程数据,包括正文段(程序指令)、数据段、堆栈段等。轮到某个进程执行的时候,尝试为这个进程在内存中找到一块空闲的区域。如果空间不足,就考虑把没有在执行的进程交换( Swap )到磁盘上,把空间腾挪出来给需要的进程。

Swap 技术在性能上存在着碎片、频繁切换等明显劣势。使用 Swap 技术,程序员需要清楚地知道自己的应用用多少内存,并且小心翼翼地使用内存,避免需要重新申请,或者研发不断扩容的算法。一般不推荐使用 swap

  1. 换出 : 把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存。
  2. 换入 : 进程再次访问这些内存的时候,把它们从磁盘读到内存中来。

swap 类型:

  1. swap 分区
  2. swap 文件

有关设置

  1. /proc/sys/vm/min_free_kbytes :一旦剩余内存小于页低阈值,就会触发内存的回收
  2. /proc/sys/vm/swappiness :调整文件页和匿名页的回收倾向 0-100,越低越不使用 swap ,为 0,当剩余内存 + 文件页小于页高阈值时,还是会发生 Swap

分析缓存命中率

缓存命中率 : 通过缓存获取数据的请求次数,占所有数据请求次数的百分比

因为 内存 的 数据读写比 磁盘读写速度快的多,所以可以依赖缓存提升速度

总结

内存优化常见思路

  1. 禁止 Swap。如果必须开启 Swap,降低 swappiness 的值,减少内存回收时 Swap 的使用倾向。
  2. 减少内存的动态分配。比如,可以使用内存池、大页(HugePage)等。
  3. 尽量使用缓存和缓冲区来访问数据。比如,可以使用堆栈明确声明内存空间,来存储需要缓存的数据;或者用 Redis 这类的外部缓存组件,优化数据的访问。
  4. 使用 cgroups 等方式限制进程的内存使用情况。这样,可以确保系统内存不会被异常进程耗尽。
  5. 通过 /proc/pid/oom_adj ,调整核心应用的 oom_score。oom_score 越小,进程就越不容易被系统杀死

常用思路和工具

内存定位流程

内存依据工具查指标

内存依据指标查工具