linux-memory-notes

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

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

调小数值 linux进程不被杀死

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

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

$ 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 是新进程可用内存的大小。

[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
# 按下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

# 清理文件页、目录项、Inodes等各种缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 每隔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
[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

[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()来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。这是两个栈和堆的例子,那么,其他内存段是否也会导致内存泄漏呢?

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

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

检测内存泄露

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

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

# -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 定期扫描内存的使用情况,并根据剩余内存落在这三个阈值的空间位置,进行内存的回收操作。

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

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

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

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

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

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

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

# 按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 | 套路篇:如何“快准狠”找到系统内存的问题?

深度好文:Linux系统内存知识