分享

性能之内存篇

 cupid 2023-06-20 发布于广东

最近笔者在看性能分析相关的是知识,就特意针对内存整理了这一篇文章,在这里笔者主要从下面三个方面来介绍这方面的知识:
1.内存的作用是什么,他在操作系统中的基础知识都有哪一些?
2.查看内存和内存相关问题涉及到的工具都有哪一些,他们的使用方式是什么样子的?
3.碰到内存问题的时候,我们需要怎么去定位呢?

一、内存的基础知识

1.内存的作用:主要用来存储系统和应用程序的指令、数据、缓存等,一般分为物理内存和虚拟内存

2.物理内存

也称为主存,大多数计算机用的主存都是动态随机访问内存(DRAM),只有内核才可以直接访问物理内存。

3.虚拟内存:

Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的,这样,进程就可以很方便地访问这些虚拟内存。

8da8ec96cc7f815ada6536171a60e26c.png

备注:进程在用户态时,只能访问用户空间内存,只有进入内核态后,才可以访问内核空间内存。虽然每个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是相同的物理内存,这样以来进程切换到内核态后,就可以很方便地访问内核空间内存。)

虚拟内存的分布介绍:

81999fc565e092305f105c3a44db1fd1.png

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

4.内存映射:不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。

dcfe1c1b2f85dd2a674464d95164fd57.png

备注:TLB 是 MMU 中页表的高速缓存,是CPU直接访问的地方。由于进程的虚拟地址空间是独立的,而 TLB 的访问速度又比 MMU 快得多,可以通过减少进程的上下文切换,减少 TLB 的刷新次数,这样来提高 TLB 缓存的使用率,进而提高 CPU 的内存访问性能。)

5.内存的分配和释放:

1)内存分配:

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

  1. brk() 方式的内存:
  2. 优点:可以减少缺页异常的发生,提高内存访问效率。
  3. 缺点:由于这些内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。

对于大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。mmap() 方式分配的内存,会在释放时直接归还系统,所以每次 mmap 都会发生缺页异常。

  1. mmap() 方式分配的内存:
  2. 优点:内存充足时,可以一次性分配好内存。
  3. 缺点:在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。

备注:当这两种调用发生后,其实并没有真正分配内存,都只在首次访问时才分配,也就是通过缺页异常进入内核中,再由内核来分配内存。

2)内存释放:

在发现内存紧张时,系统就会通过一系列机制来回收内存,通常是下面这三种方式:

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

内存回收主要有两种方式:

方式一:直接内存回收:有新的大块内存分配请求,但是剩余内存不足,这个时候系统就需要回收一部分内存(比如前面提到的缓存),进而尽可能地满足新内存请求,这个过程通常被称为直接内存回收。
方式二定期回收内存:一个专门的内核线程用来定期回收内存,也就是 kswapd0。为了衡量内存的使用情况,kswapd0 定义了三个内存阈值(watermark,也称为水位),分别是页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high),剩余内存,则使用 pages_free 表示。

备注:这个页低阈值,其实可以通过内核选项 /proc/sys/vm/min_free_kbytes 来间接设置。)

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

75a97935d66c6edb7a485d95ef90e7fa.png

回收过程:一旦剩余内存小于页低阈值,就会触发内存的回收。

在内存资源紧张时,Linux 通过直接内存回收和定期扫描的方式,来释放文件页和匿名页,以便把内存分配给更需要的进程使用。
文件页的回收比较容易理解,直接清空缓存,或者把脏数据写回磁盘后,再释放缓存就可以了。
对不常访问的匿名页,则需要通过 Swap 换出到磁盘中,这样在下次访问的时候,再次从磁盘换入到内存中就可以了。
备注:开启 Swap 后,你可以设置 /proc/sys/vm/min_free_kbytes ,来调整系统定期回收内存的阈值,也可以设置 /proc/sys/vm/swappiness ,来调整文件页和匿名页的回收倾向。)

ii.通过Swap回收不常访问的内存:把不常用的内存通过交换分区直接写到磁盘中,通常会用到交换分区(以下简称 Swap),Swap 把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用,再次访问这些内存时,重新从磁盘读入内存就可以了。

     Swap :就是把一块磁盘空间当成内存来用,它可以把进程暂时不用的数据存储到磁盘中(这个过程称为换出),当进程访问这些内存时,再从磁盘读取这些数据到内存中(这个过程称为换入)。通常只在内存不足时,才会发生 Swap 交换,并且由于磁盘读写的速度远比内存慢,Swap 会导致严重的内存性能问题。

  1. # 创建Swap文件
  2. $ fallocate -l 8G /mnt/swapfile
  3. # 修改权限只有根用户可以访问
  4. $ chmod 600 /mnt/swapfile
  5. # 配置Swap文件
  6. $ mkswap /mnt/swapfile
  7. # 开启Swap
  8. $ swapon /mnt/swapfile
  9. # 关闭SWAP
  10. $ swapoff -a && swapon -a

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

  1. OOM(Out of Memory):是内核的一种保护机制,它监控进程的内存使用情况。
  2. 评分规则:使用 oom_score 为每个进程的内存使用情况进行评分:
  3. 1.一个进程消耗的内存越大,oom_score 就越大;
  4. 2. 一个进程运行占用的 CPU 越多,oom_score 就越小。
  5. 进程的 oom_score 越大,代表消耗的内存越多,也就越容易被 OOM 杀死,从而可以更好保护系统。

备注:管理员可以通过 /proc 文件系统,手动设置进程的 oom_adj ,从而调整进程的 oom_score。

  1. # oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死;
  2. # 数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。
  3. echo -16 > /proc/$(pidof sshd)/oom_adj

6. buffer与cache:

Buffers: 是内核缓冲区用到的内存,对应的是  /proc/meminfo 中的 Buffers 值。
Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
Cache :是内核页缓存和 Slab 用到的内存,对应的是  /proc/meminfo 中的 Cached 与 SReclaimable 之和。

Cached :从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。

Slab:包括两部分其中的可回收部分用SReclaimable记录而不可回收部分,用 SUnreclaim 记录。

  1. buffers:  Memory used by kernel buffers (Buffers in /proc/meminfo)
  2. cache: Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
  3. buff/cache: Sum of buffers and cache
  4. Buffers %lu
  5.     Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).
  6. Cached %lu
  7. In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
  8. ...
  9. %lu (since Linux 2.6.19)
  10.     Part of Slab, that might be reclaimed, such as caches. 
  11. SUnreclaim %lu (since Linux 2.6.19)
  12. Part of Slab, that cannot be reclaimed on memory pressure.

Cache 和Buffer区别:

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

Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。

理论上,一个文件读首先到Block Buffer, 然后到Page Cache。有了文件系统才有了Page Cache,在老的Linux上这两个Cache是分开的。那这样对于文件数据,会被Cache两次。这种方案虽然简单,但低效,后期Linux把这两个Cache统一了。对于文件,Page Cache指向Block Buffer,对于非文件则是Block Buffer。这样就如文件实验的结果,文件操作,只影响Page Cache,Raw操作,则只影响Buffer。比如一此VM虚拟机,则会越过File System,只接操作 Disk, 常说的Direct IO.

备注:磁盘是一个块设备,可以划分为不同的分区;在分区之上再创建文件系统,挂载到某个目录,之后才可以在这个目录中读写文件。在读写普通文件时,会经过文件系统,由文件系统负责与磁盘交互;而读写磁盘或者分区时,就会跳过文件系统,也就是所谓的“裸I/O“。这两种读写方式所使用的缓存是不同的,也就是文中所讲的 Cache 和 Buffer 区别。)

缓存的命中率:所谓缓存命中率,是指直接通过缓存获取数据的请求次数,占所有数据请求次数的百分比。命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好。

二、工具和命令介绍

1.free:

  1. # Mem:物理内存,Swap:交换分区
  2. # total: 内存的总大小;
  3. # used:是已使用内存的大小,包含了共享内存; 
  4. # free:未使用内存的大小
  5. # shared:是共享内存的大小
  6. # buff:buffer的大小
  7. # cache: cache的大小
  8. # available: 新进程可用内存的大小
  9. $ free
  10. total used free shared buff/cache available
  11. Mem: 8169348 263524 6875352 668 1030472 7611064
  12. Swap: 0 0 0

2.top

  1. # 按下M切换到内存排序
  2. $ top
  3. ...
  4. KiB Mem : 8169348 total, 6871440 free, 267096 used, 1030812 buff/cache
  5. KiB Swap: 0 total, 0 free, 0 used. 7607492 avail Mem
  6. # VIRT: 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内
  7. # RES: 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
  8. # SHR: 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
  9. # MEM: 是进程使用物理内存占系统总内存的百分比。
  10. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  11. 430 root 19 -1 122360 35588 23748 S 0.0 0.4 0:32.17 systemd-journal
  12.  1075 root      20   0  771860  22744  11368 S   0.0  0.3   0:38.89 snapd
  13. ...

3.vmstat

  1. # 每隔1秒输出1组数据
  2. # bi 和 bo 则分别表示块设备读取和写入的大小,单位为块 / 秒。
  3. # 因为 Linux 中块的大小是 1KB,所以这个单位也就等价于 KB/s。
  4. $ vmstat 1
  5. procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
  6. r b swpd free buff cache si so bi bo in cs us sy id wa st
  7. 0 0 0 7743608 1112 92168 0 0 0 0 52 152 0 1 100 0 0
  8. 0 0 0 7743608 1112 92168 0 0 0 0 36 92 0 0 100 0 0

内存的 free 列在不停的变化,并且是下降趋势;而 buffer 和 cache 基本保持不变。未使用内存在逐渐减小,而 buffer 和 cache 基本不变,这说明,系统中使用的内存一直在升高,但这并不能说明有内存泄漏,因为应用程序运行中需要的内存也可能会增大。

4.cachestat /cachetop

cachestat 提供了整个操作系统缓存的读写命中情况。

cachetop 提供了每个进程的缓存命中情况。

  1. # TOTAL :表示总的 I/O 次数
  2. # MISSES ,表示缓存未命中的次数
  3. # HITS ,表示缓存命中的次数;
  4. # DIRTIES, 表示新增到缓存中的脏页数;
  5. # BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位;
  6. # CACHED_MB 表示 Cache 的大小,以 MB 为单位。
  7. $ cachestat 1 3
  8. TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB
  9. 2 0 2 1 17 279
  10. 2 0 2 1 17 279
  11. 2 0 2 1 17 279
  12.  # READ_HIT 和 WRITE_HIT ,分别表示读和写的缓存命中率
  13. $ cachetop
  14. 11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending
  15. PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
  16. 13029 root python 1 0 0 100.0% 0.0%

5. pcstat

  1. $ pcstat /bin/ls
  2. +---------+----------------+------------+-----------+---------+
  3. | Name | Size (bytes) | Pages | Cached | Percent |
  4. |---------+----------------+------------+-----------+---------|
  5. | /bin/ls | 133792 | 33 | 0 | 000.000 |
  6. +---------+----------------+------------+-----------+---------+

6.strace // 观察系统调用

  1. # strace -p $(pgrep app)
  2. strace: Process 4988 attached
  3. restart_syscall(<\.\.\. resuming interrupted nanosleep \.\.\.>) = 0
  4. openat(AT_FDCWD, "/dev/sdb1", O_RDONLY|O_DIRECT) = 4
  5. mmap(NULL, 33558528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f448d240000
  6. read(4, "8vq\213\314\264u\373\4\336K\224\25@\371\1\252\2\262\252q\221\n0\30\225bD\252\266@J"\.\.\., 33554432) = 33554432
  7. write(1, "Time used: 0.948897 s to read 33"\.\.\., 45) = 45
  8. close(4) = 0

从 strace 的结果可以看到,案例应用调用了 openat 来打开磁盘分区 /dev/sdb1,并且传入的参数为 O_RDONLY|O_DIRECT(中间的竖线表示或)。O_RDONLY 表示以只读方式打开,而 O_DIRECT 则表示以直接读取的方式打开,这会绕过系统的缓存。

7.memleak

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

8.sar

  1. #kbcommit,表示当前系统负载需要的内存。它实际上是为了保证系统内存不溢出,对需要内存的估计值。
  2. # commit,就是这个值相对总内存的百分比。
  3. # kbactive,表示活跃内存,也就是最近使用过的内存,一般不会被系统回收。
  4. # kbinact,表示非活跃内存,也就是不常访问的内存,有可能会被系统回收。
  5. # kbmemfree:剩余内存
  6. # 间隔1秒输出一组数据
  7. # -r表示显示内存使用情况,-S表示显示Swap使用情况
  8. $ sar -r -S 1
  9. 04:39:56 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
  10. 04:39:57 6249676 6839824 1919632 23.50 740512 67316 1691736 10.22 815156 841868 4
  11. 04:39:56 kbswpfree kbswpused %swpused kbswpcad %swpcad
  12. 04:39:57 8388604 0 0.00 0 0.00
  13. 04:39:57 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
  14. 04:39:58 6184472 6807064 1984836 24.30 772768 67380 1691736 10.22 847932 874224 20
  15. 04:39:57 kbswpfree kbswpused %swpused kbswpcad %swpcad
  16. 04:39:58 8388604 0 0.00 0 0.00
  17. 04:44:06 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
  18. 04:44:07 152780 6525716 8016528 98.13 6530440 51316 1691736 10.22 867124 6869332 0
  19. 04:44:06 kbswpfree kbswpused %swpused kbswpcad %swpcad
  20. 04:44:07 8384508 4096 0.05 52 1.27

三、

1. 分析内存的性能瓶颈,可以按照下面的指标依次判断:

  1. 首先,是系统内存使用情况,比如已用内存、剩余内存、共享内存、可用内存、缓存和缓冲区的用量等。
  2. 其次,是进程内存使用情况,比如进程的虚拟内存、常驻内存、共享内存以及 Swap 内存、缺页异常等。
  3. 再次,就是 Swap 的使用情况,比如 Swap 的已用空间、剩余空间、换入速度和换出速度等。

131c01399f2f31727c8289cf56141f89.png

2.按照下面的几个步骤进行依次分析:

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

bd8109bb7189f1d5cfd31e4797148f0e.png

参考资料:

《Operating System Concepts》

https://blog./hack-the-virtual-memory-malloc-the-heap-the-program-breakLinux 性能优化实战

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多