最近笔者在看性能分析相关的是知识,就特意针对内存整理了这一篇文章,在这里笔者主要从下面三个方面来介绍这方面的知识: 1.内存的作用是什么,他在操作系统中的基础知识都有哪一些? 2.查看内存和内存相关问题涉及到的工具都有哪一些,他们的使用方式是什么样子的? 3.碰到内存问题的时候,我们需要怎么去定位呢?
一、内存的基础知识
1.内存的作用:主要用来存储系统和应用程序的指令、数据、缓存等,一般分为物理内存和虚拟内存。
2.物理内存:
也称为主存,大多数计算机用的主存都是动态随机访问内存(DRAM),只有内核才可以直接访问物理内存。
3.虚拟内存:
Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的,这样,进程就可以很方便地访问这些虚拟内存。
(备注:进程在用户态时,只能访问用户空间内存,只有进入内核态后,才可以访问内核空间内存。虽然每个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是相同的物理内存,这样以来进程切换到内核态后,就可以很方便地访问内核空间内存。)
虚拟内存的分布介绍:
一旦程序运行超出了这个局部变量的作用域,栈内存就会被系统自动回收,所以不会产生内存泄漏的问题。 除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free() 来释放它们。 3. 只读段:包括程序的代码和常量,由于是只读的,不会再去分配新的内存,所以也不会产生内存泄漏。 4. 数据段:包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。 5. 内存映射段:包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。 如果程序在分配后忘了回收,就会导致跟堆内存类似的泄漏问题。
4.内存映射:不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。
(备注:TLB 是 MMU 中页表的高速缓存,是CPU直接访问的地方。由于进程的虚拟地址空间是独立的,而 TLB 的访问速度又比 MMU 快得多,可以通过减少进程的上下文切换,减少 TLB 的刷新次数,这样来提高 TLB 缓存的使用率,进而提高 CPU 的内存访问性能。)
5.内存的分配和释放:
1)内存分配:
对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存,这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。
缺点:由于这些内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。
对于大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。mmap() 方式分配的内存,会在释放时直接归还系统,所以每次 mmap 都会发生缺页异常。
缺点:在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。
备注:当这两种调用发生后,其实并没有真正分配内存,都只在首次访问时才分配,也就是通过缺页异常进入内核中,再由内核来分配内存。
2)内存释放:
在发现内存紧张时,系统就会通过一系列机制来回收内存,通常是下面这三种方式:
内存回收:比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面。
内存回收主要有两种方式:
方式一:直接内存回收:有新的大块内存分配请求,但是剩余内存不足,这个时候系统就需要回收一部分内存(比如前面提到的缓存),进而尽可能地满足新内存请求,这个过程通常被称为直接内存回收。 方式二:定期回收内存:一个专门的内核线程用来定期回收内存,也就是 kswapd0。为了衡量内存的使用情况,kswapd0 定义了三个内存阈值(watermark,也称为水位),分别是页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high),剩余内存,则使用 pages_free 表示。
(备注:这个页低阈值,其实可以通过内核选项 /proc/sys/vm/min_free_kbytes 来间接设置。)
kswapd0 定期扫描内存的使用情况,并根据剩余内存落在这三个阈值的空间位置,进行内存的回收操作。
回收过程:一旦剩余内存小于页低阈值,就会触发内存的回收。
在内存资源紧张时,Linux 通过直接内存回收和定期扫描的方式,来释放文件页和匿名页,以便把内存分配给更需要的进程使用。 文件页的回收比较容易理解,直接清空缓存,或者把脏数据写回磁盘后,再释放缓存就可以了。 对不常访问的匿名页,则需要通过 Swap 换出到磁盘中,这样在下次访问的时候,再次从磁盘换入到内存中就可以了。 (备注:开启 Swap 后,你可以设置 /proc/sys/vm/min_free_kbytes ,来调整系统定期回收内存的阈值,也可以设置 /proc/sys/vm/swappiness ,来调整文件页和匿名页的回收倾向。)
ii.通过Swap回收不常访问的内存:把不常用的内存通过交换分区直接写到磁盘中,通常会用到交换分区(以下简称 Swap),Swap 把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用,再次访问这些内存时,重新从磁盘读入内存就可以了。
Swap :就是把一块磁盘空间当成内存来用,它可以把进程暂时不用的数据存储到磁盘中(这个过程称为换出),当进程访问这些内存时,再从磁盘读取这些数据到内存中(这个过程称为换入)。通常只在内存不足时,才会发生 Swap 交换,并且由于磁盘读写的速度远比内存慢,Swap 会导致严重的内存性能问题。
$ fallocate -l 8G /mnt/swapfile $ chmod 600 /mnt/swapfile $ swapoff -a && swapon -a
iii.OOM(Out of Memory)杀死进程:内存紧张时系统还会通过 OOM ,直接杀掉占用大量内存的进程。
OOM(Out of Memory):是内核的一种保护机制,它监控进程的内存使用情况。 评分规则:使用 oom_score 为每个进程的内存使用情况进行评分: 1.一个进程消耗的内存越大,oom_score 就越大; 2. 一个进程运行占用的 CPU 越多,oom_score 就越小。 进程的 oom_score 越大,代表消耗的内存越多,也就越容易被 OOM 杀死,从而可以更好保护系统。
备注:管理员可以通过 /proc 文件系统,手动设置进程的 oom_adj ,从而调整进程的 oom_score。
# oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死; # 数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。 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 记录。
buffers: Memory used by kernel buffers (Buffers in /proc/meminfo) cache: Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo) buff/cache: Sum of buffers and cache Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so). In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached. Part of Slab, that might be reclaimed, such as caches. SUnreclaim %lu (since Linux 2.6.19) 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:
# used:是已使用内存的大小,包含了共享内存; total used free shared buff/cache available Mem: 8169348 263524 6875352 668 1030472 7611064
2.top
KiB Mem : 8169348 total, 6871440 free, 267096 used, 1030812 buff/cache KiB Swap: 0 total, 0 free, 0 used. 7607492 avail Mem # VIRT: 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内 # RES: 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。 # SHR: 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。 # MEM: 是进程使用物理内存占系统总内存的百分比。 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 430 root 19 -1 122360 35588 23748 S 0.0 0.4 0:32.17 systemd-journal 1075 root 20 0 771860 22744 11368 S 0.0 0.3 0:38.89 snapd
3.vmstat
# bi 和 bo 则分别表示块设备读取和写入的大小,单位为块 / 秒。 # 因为 Linux 中块的大小是 1KB,所以这个单位也就等价于 KB/s。 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
内存的 free 列在不停的变化,并且是下降趋势;而 buffer 和 cache 基本保持不变。未使用内存在逐渐减小,而 buffer 和 cache 基本不变,这说明,系统中使用的内存一直在升高,但这并不能说明有内存泄漏,因为应用程序运行中需要的内存也可能会增大。
4.cachestat /cachetop
cachestat 提供了整个操作系统缓存的读写命中情况。
cachetop 提供了每个进程的缓存命中情况。
# BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位; # CACHED_MB 表示 Cache 的大小,以 MB 为单位。 TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB # READ_HIT 和 WRITE_HIT ,分别表示读和写的缓存命中率 11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% 13029 root python 1 0 0 100.0% 0.0%
5. pcstat
+---------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |---------+----------------+------------+-----------+---------| | /bin/ls | 133792 | 33 | 0 | 000.000 | +---------+----------------+------------+-----------+---------+
6.strace // 观察系统调用
strace: Process 4988 attached restart_syscall(<\.\.\. resuming interrupted nanosleep \.\.\.>) = 0 openat(AT_FDCWD, "/dev/sdb1", O_RDONLY|O_DIRECT) = 4 mmap(NULL, 33558528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f448d240000 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 write(1, "Time used: 0.948897 s to read 33"\.\.\., 45) = 45
从 strace 的结果可以看到,案例应用调用了 openat 来打开磁盘分区 /dev/sdb1,并且传入的参数为 O_RDONLY|O_DIRECT(中间的竖线表示或)。O_RDONLY 表示以只读方式打开,而 O_DIRECT 则表示以直接读取的方式打开,这会绕过系统的缓存。
7.memleak
memleak 可以跟踪系统或指定进程的内存分配、释放请求,然后定期输出一个未释放内存和相应调用栈的汇总情况(默认 5 秒)。
8.sar
#kbcommit,表示当前系统负载需要的内存。它实际上是为了保证系统内存不溢出,对需要内存的估计值。 # kbactive,表示活跃内存,也就是最近使用过的内存,一般不会被系统回收。 # kbinact,表示非活跃内存,也就是不常访问的内存,有可能会被系统回收。 # -r表示显示内存使用情况,-S表示显示Swap使用情况 04:39:56 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty 04:39:57 6249676 6839824 1919632 23.50 740512 67316 1691736 10.22 815156 841868 4 04:39:56 kbswpfree kbswpused %swpused kbswpcad %swpcad 04:39:57 8388604 0 0.00 0 0.00 04:39:57 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty 04:39:58 6184472 6807064 1984836 24.30 772768 67380 1691736 10.22 847932 874224 20 04:39:57 kbswpfree kbswpused %swpused kbswpcad %swpcad 04:39:58 8388604 0 0.00 0 0.00 04:44:06 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty 04:44:07 152780 6525716 8016528 98.13 6530440 51316 1691736 10.22 867124 6869332 0 04:44:06 kbswpfree kbswpused %swpused kbswpcad %swpcad 04:44:07 8384508 4096 0.05 52 1.27
三、
1. 分析内存的性能瓶颈,可以按照下面的指标依次判断:
首先,是系统内存使用情况,比如已用内存、剩余内存、共享内存、可用内存、缓存和缓冲区的用量等。 其次,是进程内存使用情况,比如进程的虚拟内存、常驻内存、共享内存以及 Swap 内存、缺页异常等。 再次,就是 Swap 的使用情况,比如 Swap 的已用空间、剩余空间、换入速度和换出速度等。
2.按照下面的几个步骤进行依次分析:
首先,用 free 和 top,查看系统整体的内存使用情况。 接着,用 vmstat 和 pidstat,查看一段时间的趋势,从而判断出内存问题的类型。 最后进行详细分析,比如内存分配分析、缓存 / 缓冲区分析、具体进程的内存使用分析等。
参考资料:
《Operating System Concepts》
https://blog./hack-the-virtual-memory-malloc-the-heap-the-program-breakLinux 性能优化实战
|