Linux 系统内存问题

这是上一篇内容的延伸,通过对一些内存问题以及性能分析工具的梳理,总结分析内存问题的思路和优化策略。

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

内存泄漏

内存泄漏的危害非常大,忘记释放的内存,不仅应用程序自己不能访问,系统也不能把他们再次分配给其他应用,内存泄露不断累积,甚至耗尽系统内存。

  1. 栈:由系统自动分配和管理。一旦程序运行超出局部变量的作用域,栈内存就会被系统自动回收,所以不会产生内存泄漏。
  2. 只读段:包括程序的代码和常量,由于是只读的,不会再去分配新的的内存,所以也不会产生内存泄漏。
  3. 数据段:包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。
  4. 堆内存由应用程序自己来分配和管理。 除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free() 来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。
  5. 内存映射段:包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。所以,如果程序在分配后忘了回收,就会导致内存泄漏。

memleak

memleak 可以跟踪系统或指定进程的内存分配、释放请求,然后定期(default 5s)输出一个未释放内存和相应调用栈的汇总情况,是专门用来检测内存泄漏的工具。(也包含在 bcc 软件包中)

1
2
3
4
5
6
7
8
9
10
11
12
$ memleak -a -p $(pidof app)
Attaching to pid 28784, Ctrl+C to quit.
[17:52:17] Top 10 stacks with outstanding allocations:
addr = 7f8eac408060 size = 8192
addr = 7f8eac40e090 size = 8192
addr = 7f8eac40a070 size = 8192
addr = 7f8eac40c080 size = 8192
32768 bytes in 4 allocations from stack
fibonacci+0x1f [app]
child+0x4f [app]
start_thread+0xdb [libpthread-2.27.so]

Swap 机制

上篇中已经说明过三种内存回收方式,其中前两种的缓存回收和 Swap 回收,都是基于 LRU 算法,也就是优先回收不常访问的内存。该算法实际上维护着 active 和 inactive 两个双向链表,active 记录活跃的内存页,inactive 记录非活跃的内存页,在回收内存时,系统就可以根据活跃程度,优先回收不活跃的内存,活跃和非活跃的内存页,按照类型的不同,又分别分为文件页和匿名页。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat /proc/meminfo
MemTotal: 4039508 kB
MemFree: 1480236 kB
MemAvailable: 3578692 kB
Buffers: 195048 kB
Cached: 2015744 kB
SwapCached: 0 kB
Active: 1022512 kB
Inactive: 1310596 kB
Active(anon): 107476 kB
Inactive(anon): 3668 kB
Active(file): 915036 kB
Inactive(file): 1306928 kB

kswapd0

在内存资源紧张时,Linux 通过直接内存回收和定期扫描的方式,来释放文件页和匿名页。

  • 直接内存回收:当有新的大块内存分配请求,但是剩余内存不足时,系统就需要回收一部分内存(比如缓存)。
  • 定期扫描:内核线程 kswapd0 会定期扫描内存的使用情况,一旦剩余内存小于 pages_low,就会触发内存回收。
    pages_free.png
    内核选项/proc/sys/vm/min_free_kbytes设置了 pages_min,而其他两个阈值与它的关系为:
    1
    2
    3
    4
    $ cat /proc/sys/vm/min_free_kbytes 
    67584
    pages_low = pages_min*5/4
    pages_high = pages_min*3/2
    所以通过调整 pages_min 的值来调整系统定期内存回收的阈值 pages_low。

swappiness

  • 文件页:包括 buffer 和 cache 以及内存映射获取的文件映射页,文件页的回收采用直接清空方式,或者把脏数据写回磁盘后再释放。
  • 匿名页:应用程序动态分配的堆内存,匿名页的回收通过 Swap 换出到磁盘中,下次访问时,再从磁盘换入到内存中。

Linux 提供了一个/proc/sys/vm/swappiness选项,用来调整使用 Swap 的积极程度。swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。注意这只是用来调整 Swap 积极程度的,即使你把它设为 0,当剩余内存 + 文件页小于 pages_high 时,依旧会发生 Swap。

1
2
$ cat /proc/sys/vm/swappiness
0

NUMA 与 Swap

在 NUMA 架构下,每个 Node 都有自己的本地内存空间,而当本地内存不足时,默认既可以从其他 Node 寻找空闲内存,也可以从本地内存回收。

1
2
3
4
5
6
7
8
9
# 查看处理器在 Node 的分布情况
$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 3944 MB
node 0 free: 1444 MB
node distances:
node 0
0: 10

node.png

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 内存域(Zone)分布
$ cat /proc/zoneinfo
Node 0, zone DMA
...
pages free 3977
min 67
low 83
high 99
...
nr_free_pages 3977
nr_zone_inactive_anon 0
nr_zone_active_anon 0
nr_zone_inactive_file 0
nr_zone_active_file 0
...
Node 0, zone DMA32
pages free 360892
min 12795
low 15993
high 19191
...
nr_free_pages 360892
nr_zone_inactive_anon 317
nr_zone_active_anon 17020
nr_zone_inactive_file 232520
nr_zone_active_file 128849
...
Node 0, zone Normal
pages free 5027
min 4033
low 5041
high 6049
...
nr_free_pages 5027
nr_zone_inactive_anon 600
nr_zone_active_anon 9891
nr_zone_inactive_file 94212
nr_zone_active_file 99925
...
Node 0, zone Movable
pages free 0
min 0
low 0
high 0
...
Node 0, zone Device
pages free 0
min 0
low 0
high 0
...

可以通过设置/proc/sys/vm/zone_reclaim_mode来调整 NUMA 本地内存的回收策略。

  • 默认为 0 ,表示既可以从其他 Node 寻找空闲内存,也可以从本地回收内存。
  • 1、2、4 都表示只回收本地内存,2 表示脏数据写回磁盘回收内存,4 表示可以用 Swap 方式回收内存。
    1
    2
    $ cat /proc/sys/vm/zone_reclaim_mode
    0

用 sar 查看 Swap

1
2
3
4
5
6
7
8
9
10
$ sar -r -S 3
09:58:59 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
09:59:02 PM 969964 0 0.00 0 0.00

09:59:02 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
09:59:05 PM 969964 0 0.00 0 0.00

09:59:05 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
09:59:08 PM 969964 0 0.00 0 0.00

工具总结

free 和 top 命令查看内存的整体使用情况;通过 vmstat 和 pidstat 命令查看一段时间内存变化趋势,确定内存问题类型;使用 cachetop 和 cachestat,slabtop 分析缓存和缓冲区,使用 psmap 分析进程内存分布,memleak 检测内存泄漏问题

内存性能指标

注意:系统调用内存分配请求后,并不会立刻为其分配物理内存,而是在请求首次访问时,通过缺页异常来分配。

  • 可以直接从物理内存中分配时,被称为次缺页异常
  • 需要磁盘 I/O 介入(比如 Swap 方式)时,被称为主缺页异常
    memory-performace.png

分析流程

mem-analyze-process.png

内存优化思路

找到内存问题的来源后,就要进行相应优化,内存调优最重要的是保证应用程序的热点数据放到内存中,并尽量减少换页。

  1. 最好禁止 Swap;如果必须开启 Swap,降低 swappiness 的值
  2. 减少内存的动态分配,可以使用内存池、HugePage等
  3. 尽量使用缓存和缓冲区来访问数据,用 Redis 这类外部缓存组件
  4. 使用 cgroup 等方式限制进程的内存使用情况,确保系统内存不会被异常进程耗尽
  5. 通过/proc/pid/oom_adj,调整核心应用的 oom_score,保证内存紧张情况下,核心应用也不会被 OOM 杀死

参考链接

本篇笔记整理自《Linux 性能优化实战》

作者

莫尔索

发布于

2020-09-22

更新于

2024-05-19

许可协议

评论