linux性能优化实战学习笔记——内存篇

说明

这篇博客用于记录学习linux性能优化实战专栏内存篇的一些总结,巩固自己知识点的同时也能够方便以后查询

linux内存工作原理

虚拟内存

linux为每个进程提供独立的地址空间,并且地址是连续的,我们称这一块空间为虚拟内存,进程只能访问这一块地址空间。
虚拟内存又分为内核空间和用户空间两部分,不同位数的操作系统,虚拟内存的地址空间范围也不同。例如:

内存映射

虚拟内存是给进程提供的地址空间,而真正存放数据则是真实的物理空间,这两者之间还需要有一个映射关系,我们成为内存映射。
linux对每个进程维护了一张页表来记录虚拟地址与物理地址的关系:

页表存储在CPU的内存管理单元MMU中,同时为了提升访问效率,提供TLB高速缓存。
内存映射是以页为单位,页大小为4KB。同时为了节约页表的存储空间,linux通过多级页表来管理内存页。

虚拟地址分布

  • 只读段,包含代码和常量
  • 数据段,包含全局变量
  • 堆,动态分配的内存,从低地址往上增长
  • 文件映射段,包括动态库,共享内存等,从高地址往下增长
  • 栈,包含局部变量和函数调用上下文等。栈大小固定,一般为8MB

内存分配和回收

malloc是C标准库提供的函数进行分配内存,实际上调用的系统函数是brk()和mmap()

  • 小内存(小于128k),使用brk来分配,也就是通过移动堆顶位置来分配,释放的时候会被缓存
  • 大内存(大于128k),使用内存映射mmap来分配, 也就是会在内存映射段分配内存
    内存分配的时候并没有真正的获取到物理内存,需要对这块内存进行访问的时候,出现缺页异常才会真正的分配物理内存。

当内存不足的时候,linux会通过一系列措施来回收内存:

  1. 回收缓存,通过特定的算法(例如LRU),回收缓存的内存页
  2. 回收不常访问的内存,并把内存通过swap机制写入到磁盘
  3. 杀死进程,通过OOM(out of memory)杀死占用量大的内存进程(可以通过oom_adj,控制进程被OOM杀死的几率)

内存查看

1
2
3
4
drecik@ubuntu:~$ free
total used free shared buff/cache available
Mem: 8144460 1817244 3483692 9264 2843524 6020660
Swap: 1942896 0 1942896

free输出分为两行,第一行是物理内存使用情况,第二行是交换分区Swap使用情况,单位都为字节

  • total:总内存大小
  • used:已经使用的内存,包含了共享内存
  • free:可以被使用的内存大小
  • shared:共享内存大小
  • buff/cache:缓存和缓冲区大小
  • available:新进程可用的内存大小(包含了free和大部分buff/cache)

另外也可以通过top命令查看系统和进程的内存情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
top - 01:20:04 up  6:00,  1 user,  load average: 0.10, 0.03, 0.01
Tasks: 277 total, 1 running, 209 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.6 us, 0.8 sy, 2.2 ni, 96.2 id, 0.1 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8144460 total, 3474980 free, 1825568 used, 2843912 buff/cache
KiB Swap: 1942896 total, 1942896 free, 0 used. 6012328 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6108 drecik 20 0 51332 4276 3492 R 35.3 0.1 0:00.08 top
1 root 20 0 160144 9540 6800 S 0.0 0.1 0:15.36 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq
7 root 20 0 0 0 0 S 0.0 0.0 0:02.99 ksoftirqd/0
8 root 20 0 0 0 0 I 0.0 0.0 0:02.64 rcu_sched
9 root 20 0 0 0 0 I 0.0 0.0 0:00.00 rcu_bh
10 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
11 root rt 0 0 0 0 S 0.0 0.0 0:00.08 watchdog/0
12 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/0
13 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/1
14 root rt 0 0 0 0 S 0.0 0.0 0:00.08 watchdog/1
15 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/1
16 root 20 0 0 0 0 S 0.0 0.0 0:01.52 ksoftirqd/1
18 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/1:0H
19 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kdevtmpfs
20 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 netns
21 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_tasks_kthre
22 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kauditd
25 root 20 0 0 0 0 S 0.0 0.0 0:00.07 khungtaskd
26 root 20 0 0 0 0 S 0.0 0.0 0:00.00 oom_reaper
27 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 writeback
28 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kcompactd0

第4和第5行为系统总的内存使用情况,字段意义跟free命令的一样
每个进程也有内存相关列:

  • VIRT:进程占用虚拟内存的大小
  • RES:进程占用物理内存的大小,不包过共享内存和Swap
  • SHR:进程占用共享内存的大小,比如与其他进程的共享内存,加载的动态链接库以及程序的代码段
  • %MEM:进程物理内存占用系统总内存的百分比

buffers/cache

  • buffers是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会很大(20MB左右)。这样内核就可以把分散的写集中起来,统一优化磁盘的写入。读取磁盘数据也会用到该缓存
  • cache是是从磁盘读取的文件页的缓存,也就是缓存从文件读取的数据。这样下次访问这些文件的数据时,就可以直接从内存中快速获取。写文件的时候也会用到该缓存。

buffers和cache可以极大提升I/O的性能。通常我们通过缓存命中率,来衡量缓存的使用效率。
linux可以通过cachestat和cachetop两个工具(bcc软件包,需要linux内核4.1以上)来获取到相应指标:

  • cachestat提供了整个系统缓存的读写命中情况。
  • cachetop 提供了每个进程的缓存命中情况。

buffers和cache都是操作系统来管理,应用程序不能控制。所以应用程序可以在内部使用自己的内存缓存组件,来进一步提升效率。

内存泄漏

通常使用工具来查看进程内存泄漏情况,例如memleak,和我比较推荐的AddressSanitizer

Swap

Swap机制是指当物理内存不足的时候,可以通过磁盘空间来当作内存使用,分为两个过程:

  • 换出:把暂时不用的内存数据存储到硬盘中,并释放这一块内存
  • 换入:进程访问换出内存的时候,从硬盘读取数据到内存

利用Swap机制可以使内存空间增大。需要注意的是Swap只针对于进程堆分配的内存(匿名页)
Swap机制通过三个阈值来控制:

  • 剩余内存小于页最小阈值,说明进程可用内存耗尽了,只有内核才可以分配内存
  • 剩余内存在页最小阈值和页低阈值之间,说明内存压力比较大,剩余内存不多,需要进行换出操作,直到内存大于高阈值为止
  • 剩余内存在页低阈值与页高阈值之间,说明内存有一定压力,但还可以满足新分配内存需求
  • 剩余内存在页高阈值上面,说明内存充足

这些阈值通过内核选项/proc/sys/vm/min_free_kbytes来控制,min_free_kbytes表示的是页最小阈值,其他两个阈值通过公式生成:

1
2
pages_low = pages_min*5/4
pages_high = pages_min*3/2

swappiness

上面提到内存占用除了匿名页外还有buffers/cache占用的内存,那内存不足的时候是如何决定是进行Swap还是释放buffers/cache占用的内存?
这个优先级可以通过配置/proc/sys/vm/swappiness控制,该值为0-100。越大表示越积极Swap,越小,越倾向于释放buffers/cache内存。

Swap升高分析

Swap本质问题还是内存不足导致,所以如果出现Swap过高的时候,首先应该查看系统和进程的内存使用情况,进而找出Swap升高的更远和受影响的进程。

Swap总结

Swap因为会与磁盘交互,影响整体的性能,所以我们应该尽量避免或者降低Swap使用:

  • 禁止Swap,现在服务器内存都比较大,大部分云服务器都是默认禁止Swap的
  • 如果实在需要用到Swap,则调低swappiness的值,减少Swap发生
  • 可以使用库函数mlock()和mlockall(),禁止内存被换出

排查内存问题的一般思路

内存性能指标

速查图:内存性能指标 -> 工具

速查图:工具 -> 内存性能指标

内存性能问题查找思路图

内存性能优化思路

  1. 最好禁止Swap
  2. 减少内存动态分配
  3. 尽量使用缓存和缓冲区访问数据
  4. 使用cgroups等方式限制内存使用情况
  5. 通过调整/proc/pid/oom_adj,防止进程在系统内存不足时被OOM杀死