linux-memory-notes

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

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

调小数值 linux进程不被杀死

echo -16 > /proc/$(pidof sshd)/oom_adj

注意不同版本的free输出可能会有所不同

1
2
3
4
5
6
7
8
9
10
$ free
total used free shared buff/cache available
Mem: 8169348 263524 6875352 668 1030472 7611064
Swap: 0 0 0

[wkq.stb@s_wkq_b ~]$ free
total used free shared buffers cached
Mem: 16334056 16135076 198980 6948 130160 228828
-/+ buffers/cache: 15776088 557968
Swap: 8388604 1042220 7346384

第一列,total 是总内存大小;
第二列,used 是已使用内存的大小,包含了共享内存;
第三列,free 是未使用内存的大小;
第四列,shared 是共享内存的大小;
第五列,buff/cache 是缓存和缓冲区的大小;
最后一列,available 是新进程可用内存的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[wkq.stb@s_wkq_b ~]$ top
top - 14:49:12 up 151 days, 18:14, 2 users, load average: 0.00, 0.00, 0.00
Tasks: 146 total, 1 running, 145 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.3%us, 0.3%sy, 0.0%ni, 99.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu1 : 0.0%us, 0.3%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 16334056k total, 16134976k used, 199080k free, 130144k buffers
Swap: 8388604k total, 1042224k used, 7346380k free, 228824k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1802 search 20 0 17.9g 14g 5276 S 0.7 92.6 826:18.86 java
3914 wkq.stb 20 0 15024 1356 1004 R 0.3 0.0 0:00.02 top
14363 root 20 0 359m 43m 4424 S 0.3 0.3 92:54.64 jdog-monitor.1.
1 root 20 0 19232 572 392 S 0.0 0.0 0:10.99 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root RT 0 0 0 0 S 0.0 0.0 4:08.02 migration/0
4 root 20 0 0 0 0 S 0.0 0.0 0:52.91 ksoftirqd/0
5 root RT 0 0 0 0 S 0.0 0.0 0:00.00 stopper/0
6 root RT 0 0 0 0 S 0.0 0.0 0:12.19 watchdog/0
7 root RT 0 0 0 0 S 0.0 0.0 3:15.61 migration/1
8 root RT 0 0 0 0 S 0.0 0.0 0:00.00 stopper/1
9 root 20 0 0 0 0 S 0.0 0.0 0:39.68 ksoftirqd/1
10 root RT 0 0 0 0 S 0.0 0.0 0:10.27 watchdog/1
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
# 按下M切换到内存排序
[wkq.stb@s_wkq_b ~]$ top
top - 14:49:55 up 151 days, 18:15, 2 users, load average: 0.00, 0.00, 0.00
Tasks: 146 total, 1 running, 145 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.1%us, 0.2%sy, 0.0%ni, 99.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 16334056k total, 16134976k used, 199080k free, 130144k buffers
Swap: 8388604k total, 1042224k used, 7346380k free, 228824k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1802 search 20 0 17.9g 14g 5276 S 0.3 92.6 826:19.00 java
5945 jd.dev 20 0 7653m 244m 5092 S 0.3 1.5 275:00.32 java
1515 root 20 0 825m 81m 4124 S 0.0 0.5 718:10.97 jcloudhids
14363 root 20 0 359m 43m 4424 S 0.3 0.3 92:54.68 jdog-monitor.1.
13321 root 20 0 748m 20m 5996 S 0.0 0.1 128:42.30 jdog-kunlunmirr
3757 root 20 0 97.7m 3936 2976 S 0.0 0.0 0:00.00 sshd
30572 root 20 0 97.7m 3932 2976 S 0.0 0.0 0:00.01 sshd
1164 root 20 0 249m 3688 568 S 0.0 0.0 0:09.71 rsyslogd
30637 wkq.stb 20 0 59980 3480 2536 S 0.0 0.0 0:05.60 ssh
1448 root 20 0 186m 3004 968 S 0.0 0.0 2:19.88 python
30574 wkq.stb 20 0 97.7m 2256 1280 S 0.0 0.0 0:08.71 sshd
3759 wkq.stb 20 0 97.7m 2252 1272 S 0.0 0.0 0:00.00 sshd
3760 wkq.stb 20 0 105m 1968 1504 S 0.0 0.0 0:00.01 bash
30575 wkq.stb 20 0 105m 1960 1508 S 0.0 0.0 0:00.00 bash
1481 root 20 0 175m 1472 1004 S 0.0 0.0 92:43.68 AgentMonitor
3958 wkq.stb 20 0 15024 1360 1004 R 0.7 0.0 0:00.06 top
862 root 20 0 22356 932 644 S 0.0 0.0 24:56.74 qemu-ga
1284 nscd 20 0 549m 920 576 S 0.0 0.0 10:17.28 nscd
1091 root 20 0 9120 700 552 S 0.0 0.0 0:00.25 dhclient
1574 root 20 0 158m 612 476 S 0.0 0.0 1:49.17 jcloudhidsupdat
1440 root 20 0 114m 608 388 S 0.0 0.0 0:39.52 crond
1 root 20 0 19232 572 392 S 0.0 0.0 0:10.99 init

top 输出界面的顶端,也显示了系统整体的内存使用情况,这些数据跟 free 类似。
接着看下面的内容,跟内存相关的几列数据,比如 VIRT、RES、SHR 以及 %MEM 等。这些数据,包含了进程最重要的几个内存使用情况,我们挨个来看。
VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
%MEM 是进程使用物理内存占系统总内存的百分比。

/proc/meminfo

1
2
# 清理文件页、目录项、Inodes等各种缓存
$ echo 3 > /proc/sys/vm/drop_caches
1
2
3
4
5
6
# 每隔1秒输出1组数据
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7743608 1112 92168 0 0 0 0 52 152 0 1 100 0 0
0 0 0 7743608 1112 92168 0 0 0 0 36 92 0 0 100 0 0
1
2
3
4
5
6
7
8
9
10
[jd.stb@s_legend_b ~]$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 1042220 199476 130192 228868 0 0 0 5 1 0 0 0 100 0 0
0 0 1042220 199452 130192 228888 0 0 0 0 370 572 0 0 100 0 0
0 0 1042220 199452 130192 228888 0 0 0 0 311 569 0 0 100 0 0
0 0 1042220 199452 130192 228888 0 0 0 0 360 591 0 0 100 0 0
0 0 1042220 199256 130192 228888 0 0 0 4 658 1096 0 0 99 0 0
0 0 1042220 199188 130200 228888 0 0 0 116 388 635 0 0 100 0 0
0 0 1042220 199188 130200 228888 0 0 0 0 351 582 0 0 100 0 0

$ dd if=/dev/urandom of=/tmp/file bs=1M count=500

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
[jd.stb@s_legend_b ~]$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 1042220 196704 130204 229204 0 0 0 5 1 0 0 0 100 0 0
0 0 1042220 196548 130204 229204 0 0 0 60 385 611 0 0 100 0 0
0 0 1042220 196604 130204 229204 0 0 0 4 308 559 0 0 100 0 0
0 0 1042220 196604 130204 229204 0 0 0 0 335 571 0 0 100 0 0
0 0 1042220 196612 130204 229204 0 0 0 4 315 580 0 0 100 0 0
0 0 1042220 196612 130204 229204 0 0 0 0 338 573 0 0 100 0 0
0 0 1042220 196628 130204 229204 0 0 0 0 342 592 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 40 361 609 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 364 670 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 352 584 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 321 580 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 351 592 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 72 627 1205 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 504 908 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 563 958 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 492 855 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 684 1392 0 1 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 100 343 570 0 0 100 0 0
0 0 1042220 196636 130204 229204 0 0 0 0 295 560 0 0 100 0 0
1 0 1042220 196636 130204 229204 0 0 52 0 566 634 0 4 96 0 0
1 0 1042220 193884 130204 231304 0 0 0 52 1304 558 0 25 75 0 0
1 0 1042220 190908 130204 234376 0 0 0 0 1324 531 0 25 75 0 0
1 0 1042220 187808 130204 237300 0 0 0 0 1278 518 0 25 75 0 0
1 0 1042220 185824 130204 239496 0 0 0 0 1368 567 0 25 75 0 0
1 0 1042220 182724 130204 242420 0 0 0 8 1322 577 0 25 75 0 0
1 0 1042220 180616 130212 244616 0 0 0 116 1558 701 0 25 75 0 0
1 0 1042220 177640 130212 247540 0 0 0 4 1355 576 0 25 75 0 0
1 0 1042220 174540 130212 250612 0 0 0 8 1425 584 0 25 75 0 0
1 0 1042220 172432 130212 252808 0 0 0 0 1298 540 0 25 75 0 0
1 0 1042220 169456 130212 255732 0 0 0 0 1338 560 0 25 75 0 0
1 0 1042220 167348 130212 257928 0 0 0 56 1348 589 0 25 75 0 0
1 0 1042220 164248 130212 260852 0 0 0 4 1388 590 0 25 75 0 0
1 0 1042220 160756 130212 263924 0 0 0 0 1662 666 0 27 72 0 1
1 0 1042220 158652 130212 266120 0 0 0 0 1345 535 0 25 75 0 0
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 1042220 155552 130212 269044 0 0 0 0 1288 532 0 25 75 0 0
1 0 1042220 153568 130212 271240 0 0 0 32 1421 652 0 26 73 0 1
1 0 1045056 168572 125980 263304 0 2836 0 2836 1394 532 0 26 74 0 0
1 0 1045056 166588 125980 265200 0 0 0 12 1309 491 0 25 75 0 0
1 0 1045056 163496 125980 268272 0 0 0 0 1231 466 0 25 75 0 0
1 0 1045056 160520 125980 271196 0 0 0 0 1309 490 0 25 75 0 0
1 0 1045056 158412 125980 273392 0 0 0 32 1292 538 0 25 75 0 0
1 0 1045056 155436 125980 276316 0 0 0 0 1371 555 0 25 75 0 0
1 0 1045056 153212 125980 278512 0 0 0 12 1307 551 0 25 75 0 0
1 0 1047004 167100 120572 271568 0 1948 0 1948 1418 574 0 26 74 0 0
1 0 1047004 164124 120572 274640 0 0 0 0 1242 489 0 25 75 0 0
1 0 1047004 162016 120572 276836 0 0 0 28 1318 524 0 25 75 0 0
1 0 1047004 158916 120572 279760 0 0 0 0 1266 486 0 25 75 0 0
1 0 1047004 156932 120572 281956 0 0 0 0 1345 598 0 25 74 0 1
1 0 1047004 153832 120572 284884 0 0 0 0 1243 476 0 25 75 0 0
1 0 1047408 166728 113704 279076 0 404 0 404 1356 506 0 26 74 0 0
1 0 1047408 164620 113704 281272 0 0 0 28 1252 498 0 25 75 0 0
1 0 1047408 161644 113704 284196 0 0 0 0 1350 584 0 25 75 0 0
1 0 1047408 159040 113708 286400 0 0 4 86096 1480 620 0 27 72 0 1
1 0 1047408 156064 113708 289324 0 0 0 0 1394 638 0 25 75 0 0
1 0 1047408 153956 113708 291520 0 0 0 0 1290 537 0 25 75 0 0
1 0 1047720 166960 105696 287132 0 312 128 344 2060 958 1 27 70 0 3
1 0 1047720 163984 105696 290056 0 0 0 0 1336 550 0 25 75 0 0
1 0 1047720 161876 105696 292252 0 0 0 0 1335 525 0 25 75 0 0
1 0 1047720 158776 105696 295176 0 0 0 0 1262 504 0 25 75 0 0
1 0 1047720 156792 105696 297372 0 0 0 4 1351 541 0 25 74 0 0
1 0 1047720 153692 105696 300224 0 0 0 52 1338 542 0 25 75 0 0
1 0 1047792 166340 97976 295228 0 72 0 72 1368 535 0 26 75 0 0
1 0 1047792 164240 97976 297424 0 0 0 8 1258 501 0 25 75 0 0
0 0 1047792 164256 97976 298300 0 0 0 0 849 548 0 13 87 0 0
0 0 1047792 164388 97976 298448 0 0 0 0 266 515 0 0 100 0 0
0 0 1047792 164388 97976 298448 0 0 0 0 404 578 0 0 100 0 0
0 0 1047792 164332 97980 298452 0 0 0 32 341 599 0 0 100 0 0

Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。

如何统计出所有进程的物理内存使用量呢?

哪些区域会内存泄露

  1. 栈内存由系统自动分配和管理。一旦程序运行超出了这个局部变量的作用域,栈内存就会被系统自动回收,所以不会产生内存泄漏的问题。

    再比如,很多时候,我们事先并不知道数据大小,所以你就要用到标准库函数malloc()_,_ 在程序中动态分配内存。这时候,系统就会从内存空间的堆中分配内存。

  2. 堆内存由应用程序自己来分配和管理。除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数free()来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。这是两个栈和堆的例子,那么,其他内存段是否也会导致内存泄漏呢?

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

  4. 数据段,包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。
  5. 最后一个内存映射段,包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。所以,如果程序在分配后忘了回收,就会导致跟堆内存类似的泄漏问题。

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

检测内存泄露

用 top 或 ps 来观察进程的内存使用情况,然后找出内存使用一直增长的进程,最后再通过 pmap 查看进程的内存分布。

memleak 可以跟踪系统或指定进程的内存分配、释放请求,然后定期输出一个未释放内存和相应调用栈的汇总情况(默认 5 秒)。

1
2
3
4
5
6
7
8
9
10
11
12
13
# -a 表示显示每个内存分配请求的大小以及地址
# -p 指定案例应用的PID号
$ /usr/share/bcc/tools/memleak -a -p $(pidof app)
WARNING: Couldn't find .text section in /app
WARNING: BCC can't handle sym look ups for /app
addr = 7f8f704732b0 size = 8192
addr = 7f8f704772d0 size = 8192
addr = 7f8f704712a0 size = 8192
addr = 7f8f704752c0 size = 8192
32768 bytes in 4 allocations from stack
[unknown] [app]
[unknown] [app]
start_thread+0xdb [libpthread-2.27.so]

从 memleak 的输出可以看到,案例应用在不停地分配内存,并且这些分配的地址没有被回收。

free()调用,释放函数fibonacci()分配的内存,修复了内存泄漏的问题。就这个案例而言,还有没有其他更好的修复方法呢?

Linux Swap

Linux 的 Swap 机制。Swap 把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。

Swap 原理

Swap 说白了就是把一块磁盘空间或者一个本地文件(以下讲解以磁盘为例),当成内存来使用。它包括换出和换入两个过程。

  1. 换出,就是把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存。
  2. 换入,则是在进程再次访问这些内存的时候,把它们从磁盘读到内存中来。
    Swap 其实是把系统的可用内存变大了。这样,即使服务器的内存不足,也可以运行大内存的应用程序。

    有一个专门的内核线程用来定期回收内存,也就是 kswapd0。
    为了衡量内存的使用情况,kswapd0 定义了三个内存阈值(watermark,也称为水位),分别是页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high)。剩余内存,则使用 pages_free 表示。

    kswapd0 定期扫描内存的使用情况,并根据剩余内存落在这三个阈值的空间位置,进行内存的回收操作。

  3. 剩余内存小于页最小阈值,说明进程可用内存都耗尽了,只有内核才可以分配内存。
  4. 剩余内存落在页最小阈值和页低阈值中间,说明内存压力比较大,剩余内存不多了。这时 kswapd0 会执行内存回收,直到剩余内存大于高阈值为止。
  5. 剩余内存落在页低阈值和页高阈值中间,说明内存有一定压力,但还可以满足新内存请求。
  6. 剩余内存大于页高阈值,说明剩余内存比较多,没有内存压力。

直接内存访问区(DMA)、普通内存区(NORMAL)、伪内存区(MOVABLE)

内存既包括了文件页,又包括了匿名页。
对文件页的回收,当然就是直接回收缓存,或者把脏页写回磁盘后再回收。
而对匿名页的回收,其实就是通过 Swap 机制,把它们写入磁盘后再释放内存。

不过,你可能还有一个问题。既然有两种不同的内存回收机制,那么在实际回收内存时,到底该先回收哪一种呢?
其实,Linux 提供了一个 /proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度。
swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。虽然 swappiness 的范围是 0-100,不过要注意,这并不是内存的百分比,而是调整 Swap 积极程度的权重,即使你把它设置成 0,当剩余内存 + 文件页小于页高阈值时,还是会发生 Swap。

在内存资源紧张时,Linux 通过直接内存回收和定期扫描的方式,来释放文件页和匿名页,以便把内存分配给更需要的进程使用。文件页的回收比较容易理解,直接清空,或者把脏数据写回磁盘后再释放。而对匿名页的回收,需要通过 Swap 换出到磁盘中,下次访问时,再从磁盘换入到内存中。

1
2
3
4
5
6
7
8
# 创建Swap文件
$ fallocate -l 8G /mnt/swapfile
# 修改权限只有根用户可以访问
$ chmod 600 /mnt/swapfile
# 配置Swap文件
$ mkswap /mnt/swapfile
# 开启Swap
$ swapon /mnt/swapfile
1
2
# 关闭 swap
$ swapoff -a
1
2
# 关闭 Swap 后再重新打开,也是一种常用的 Swap 空间清理方法
$ swapoff -a && swapon -a

查看 swappiness 的配置 cat /proc/sys/vm/swappiness

1
2
3
4
5
6
7
# 按VmSwap使用量对进程排序,输出进程名称、进程ID以及SWAP用量
$ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head
dockerd 2226 10728 kB
docker-containe 2251 8516 kB
snapd 936 4020 kB
networkd-dispat 911 836 kB
polkitd 1004 44 kB

内存性能指标

快速定位系统内存问题

为了迅速定位内存问题,我通常会先运行几个覆盖面比较大的性能工具,比如 free、top、vmstat、pidstat 等。具体的分析思路主要有这几步。

  1. 先用 free 和 top,查看系统整体的内存使用情况。
  2. 再用 vmstat 和 pidstat,查看一段时间的趋势,从而判断出内存问题的类型。
  3. 最后进行详细分析,比如内存分配分析、缓存 / 缓冲区分析、具体进程的内存使用分析等。

References

[1] 01| 基础篇:Linux内存是怎么工作的?
[2] 02 | 基础篇:怎么理解内存中的Buffer和Cache?
[3] 03 | 案例篇:如何利用系统缓存优化程序的运行效率?
[4] 04 | 案例篇:内存泄漏了,我该如何定位和处理?
[5] 05 | 案例篇:为什么系统的Swap变高了(上)
[6] 06 | 案例篇:为什么系统的Swap变高了?(下)
[7] 07 | 套路篇:如何“快准狠”找到系统内存的问题?