Lework Study hard, improve every day.

Linux性能调优之 内存

2019-10-22
lework
本文 7174 字,阅读全文约需 21 分钟

内存相关术语

内存映射

内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系。

当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常

为决页表项过多的问题,Linux 提供了两种机制,也就是多级页表和大页(HugePage)

  • 多级页表就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移。
  • 大页,顾名思义,就是比普通页更大的内存块,常见的大小有 2MB 和 1GB。大页通常用在使用大量内存的进程上,比如 Oracle、DPDK 等。

### 虚拟内存空间分布

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

内存分配与回收

分配

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

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

  • 而大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。

    mmap() 方式分配的内存,会在释放时直接归还系统,所以每次 mmap 都会发生缺页异常。在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。这也是 malloc 只对大块内存使用 mmap 的原因。

在内核空间,Linux 则通过 slab 分配器来管理小内存。

回收内存

  • 回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面;

  • 回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中;

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

  • 直接内存回收,尽可能地满足新内存请求,进而回收一部分内存

  • kswapd0内核线程定时回收内存

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

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

    通过内核选项 /proc/sys/vm/min_free_kbytes来设置页低阈值,而其他两个阈值,都是根据页最小阈值计算生成的pages_low = pages_min*5/4, pages_high = pages_min*3/2

  • NUMA (Non-UniformMemory Access) 架构下的内存回收。

    /proc/sys/vm/zone_reclaim_mode 来调整numa node的内存回收策略

    默认的 0 ,也就是刚刚提到的模式,表示既可以从其他 Node 寻找空闲内存,也可以从本地回收内存。 1、2、4 都表示只回收本地内存,2 表示可以回写脏数据回收内存,4 表示可以用Swap 方式回收内存。

Buffers 和 Cache

Buffers 是内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值。 Cache 是内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached 与SReclaimable 之和。

Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。 Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。

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

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

缓存命中率

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

内存泄漏

堆内存由应用程序自己来分配和管理。除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free() 来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。

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

文件页,脏页

属于可回收内存的缓存和缓冲区,是文件页

被应用程序修改过,并且暂时还没写入磁盘的数据就是脏页

应用程序动态分配的堆内存被称为匿名页

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

swap

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

  • 所谓换出,就是把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存。
  • 而换入,则是在进程再次访问这些内存的时候,把它们从磁盘读到内存中来。

/proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度。

swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。

在内存资源紧张时,Linux 会通过 Swap ,把不常访问的匿名页换出到磁盘中,下次访问的时候再从磁盘换入到内存中来。你可以设置 /proc/sys/vm/min_free_kbytes,来调整系统定期回收内存的阈值;也可以设置/proc/sys/vm/swappiness,来调整文件页和匿名页的回收倾向。

内存性能指标

内存性能指标
├── 系统内存指标
│   ├── 已用内存
│   ├── 剩余内存
│   ├── 可用内存
│   ├── 缺页异常
│   │	├── 主缺页异常
│   │   └── 次缺页异常
│   ├── 缓存/缓冲区
│   └── Slabs
├── 进程内存指标
│   ├── 虚拟内存(VSS)
│   ├── 常驻内存(RSS)
│   ├── 按比例分配共享内存后的物理内存(PSS)
│   ├── 独占内存(USS)
│   ├── 共享内存
│   ├── SWAP内存
│   └── 缺页异常
│   │	├── 主缺页异常
│   │   └── 次缺页异常
└── SWAP
    ├── 已用空间
    ├── 剩余空间
    ├── 换入速度
    └── 换出速度

系统的已用内存、剩余内存、共享内存、可用内存、缓存和缓冲区的用量

  • 已用内存和剩余内存很容易理解,就是已经使用和还未使用的内存。
  • 共享内存是通过 tmpfs 实现的,所以它的大小也就是 tmpfs 使用的内存大小。tmpfs其实也是一种特殊的缓存。
  • 可用内存是新进程可以使用的最大内存,它包括剩余内存和可回收缓存。
  • 缓存包括两部分,一部分是磁盘读取文件的页缓存,用来缓存从磁盘读取的数据,可以加快以后再次访问的速度。另一部分,则是 Slab 分配器中的可回收内存。
  • 缓冲区是对原始磁盘块的临时存储,用来缓存将要写入磁盘的数据。这样,内核就可以把分散的写集中起来,统一优化磁盘写入。

进程内存使用情况,比如进程的虚拟内存、常驻内存、共享内存以及 Swap 内存等。

  • 虚拟内存,包括了进程代码段、数据段、共享内存、已经申请的堆内存和已经换出的内存等。这里要注意,已经申请的内存,即使还没有分配物理内存,也算作虚拟内存。
  • 常驻内存是进程实际使用的物理内存,不过,它不包括 Swap 和共享内存。
  • 共享内存,既包括与其他进程共同使用的真实的共享内存,还包括了加载的动态链接库以及程序的代码段等。
  • Swap 内存,是指通过 Swap 换出到磁盘的内存。

缺页异常

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

Swap 的使用情况,比如 Swap 的已用空间、剩余空间、换入速度和换出速度等。

  • 已用空间和剩余空间很好理解,就是字面上的意思,已经使用和没有使用的内存空间。
  • 换入和换出速度,则表示每秒钟换入和换出内存的大小。

## 内存性能工具

案例

在缓存和缓冲区的原理篇中,

我们通过 proc 文件系统,找到了内存指标的来源; 并通过 vmstat,动态观察了内存的变化情况。与 free 相比,vmstat 除了可以动态查看内存变化,还可以区分缓存和缓冲区、Swap 换入和换出的内存大小。

为了弄清楚缓存的命中情况,我们又用了 cachestat,查看整个系统缓存的读写命中情况,并用 cachetop 来观察每个进程缓存的读写命中情况。

在内存泄漏的案例中,我们用 vmstat,发现了内存使用在不断增长,又用memleak,确认发生了内存泄漏。通过 memleak 给出的内存分配栈,我们找到了内存泄漏的可疑位置。

在 Swap 的案例中,我们用 sar 发现了缓冲区和 Swap 升高的问题。通过cachetop,我们找到了缓冲区升高的根源;通过对比剩余内存跟 /proc/zoneinfo 的内存阈,我们发现 Swap 升高是内存回收导致的。案例最后,我们还通过 /proc 文件系统,找出了 Swap 所影响的进程。

根据指标找工具

内存指标 性能工具
系统已用、可用、剩余内存 free
vmstat
sar
/proc/meminfo
进程虚拟内存、常驻内存、共享内存 ps
top
进程内存分布 pmap
进程Swap换出内存 top
/proc/pid/status
进程缺页异常 top
系统换页情况 sar
free
vmstat
缓存/缓冲区用量 sar
cachestat
缓存/缓冲区命中率 cachetop
free
SWAP已用空间和剩余空间 sar
Swap换入换出 vmstat
memleak
内存泄漏检测 valgrind
指定文件的缓存大小 pcstat

根据工具查指标

性能工具 内存指标
free
/proc/meminfo
系统已用、可用、剩余内存以及缓存和缓冲区的使用量
top
ps
进程虚拟、常驻、共享内存以及缺页异常
vmstat 系统剩余内存、缓存、缓冲区、换入、换出
sar 系统内存换页情况、内存使用率、缓存和缓冲区用量以及Swap使用情况
cachestat 系统缓存和缓冲区的命中率
cachetop 进程缓存和缓冲区的命中率
slabtop 系统Slab缓存使用情况
/proc/pid/status 进程Swap内存等
/proc/pid/smaps 进程地址空间和内存状态
pmap 进程内存错误检查器,用来检测内存初始化、
valgrind 泄漏、越界访问等各种内存错误
memleak 内存泄漏检测
pcstat 查看指定文件的缓存情况

快速分析内存的性能瓶颈

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

分析流程图

linux15

  1. 通过 free,发现大部分内存都被缓存占用后
  2. 可以使用 vmstat 或者 sar观察一下缓存的变化趋势,确认缓存的使用是否还在继续增大。
  3. 如果继续增大,则说明导致缓存升高的进程还在运行,那你就能用缓存 / 缓冲区分析工具(比如 cachetop、slabtop 等),分析这些缓存到底被哪里占用。
  4. 如果发现系统可用内存不足时,首先要确认内存是否被缓存 / 缓冲区占用。排除缓存 / 缓冲区后,你可以继续用 pidstat 或者 top,定位占用内存最多的进程。
  5. 找出进程后,再通过进程内存空间工具(比如 pmap),分析进程地址空间中内存的使用情况就可以了。
  6. 通过 vmstat 或者 sar 发现内存在不断增长后,可以分析中是否存在内存泄漏的问题。比如你可以使用内存分配分析工具 memleak ,检查是否存在内存泄漏。如果存在内存泄漏问题,memleak 会为你输出内存泄漏的进程以及调用堆栈。

内存常见优化

  1. 最好禁止 Swap。如果必须开启 Swap,降低 swappiness 的值,减少内存回收时Swap 的使用倾向。
  2. 减少内存的动态分配。比如,可以使用内存池、大页(HugePage)等。
  3. 尽量使用缓存和缓冲区来访问数据。比如,可以使用堆栈明确声明内存空间,来存储需要缓存的数据;或者用 Redis 这类的外部缓存组件,优化数据的访问。
  4. 使用 cgroups 等方式限制进程的内存使用情况。这样,可以确保系统内存不会被异常进程耗尽。
  5. 通过 /proc/pid/oom_adj ,调整核心应用的 oom_score。这样,可以保证即使内存紧张,核心应用也不会被 OOM 杀死。

常见工具的使用

free

显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer

常用命令

free
free -m
free -g

指标含义

total:总计物理内存的大小。
used:已使用多大。
free:可用有多少。
Shared:多个进程共享的内存总额。
Buffers/cached:磁盘缓存的大小。
available

cachestat

提供了整个操作系统缓存的读写命中情况。 需要安装 bcc 软件包 。

常用命令

cachestat 1 3
TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB
2 0 2 1 17 279
2 0 2 1 17 279
2 0 2 1 17 279

指标含义

TOTAL 表示总的 I/O 次数
MISSES 表示缓存未命中的次数
HITS  表示缓存命中的次数
DIRTIES 表示新增到缓存中的脏页数
BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位
CACHED_MB 表示 Cache 的大小,以 MB 为单位

### cachetop

提供了每个进程的缓存命中情况。 需要安装 bcc 软件包 。

cachetop
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%

指标含义

默认按照缓存的命中次数(HITS)排序,

HITS 表示缓存命中的次数
MISSES 表示缓存未命中的次数
DIRTIES 表示新增到缓存中的脏页数
READ_HIT 表示读的缓存命中率
WRITE_HIT 表示写的缓存命中率

### slabtop

以实时的方式显示内核“slab”缓冲区的细节信息

常用命令

slabtop
slabtop -d 1 # 1秒钟刷新一次数据
slabtop -s c # 按照cache size排序

pmap

用于显示一个或多个进程的内存状态

常用命令

pmap $PID

名称解释

Address: 内存开始地址
Kbytes: 占用内存的字节数(KB)
RSS: 保留内存的字节数(KB)
Dirty: 脏页的字节数(包括共享和私有的)(KB)
Mode: 内存的权限:read、write、execute、shared、private (写时复制)
Mapping: 占用内存的文件、或[anon](分配的内存)、或[stack](堆栈)
Offset: 文件偏移
Device: 设备名 (major:minor)

valgrind

valgrind是一个专业的程序优化和内存调试工具集。

常用的工具包括memcheckcachegrindcallgrindmassifhelgrinddrd

安装

yum -y install make gcc autoconf automake
wget https://sourceware.org/pub/valgrind/valgrind-3.15.0.tar.bz2
tar -xjvf valgrind-3.15.0.tar.bz2
cd valgrind-3.15.0
./autogen.sh
./configure
make
make install
valgrind --version

常用命令

valgrind   # 使用Memcheck(默认)工具显示program对内存使用情况的诊断
valgrind --leak-check=full --show-leak-kinds=all  # 使用Memcheck详细输出program的所有可能的内存泄漏
valgrind --tool=cachegrind  # 使用Cachegrind工具来分析和记录program的CPU缓存操作
valgrind --tool=massif --stacks=yes  # 使用Massif工具来分析和记录program的堆内存和堆栈使用情况

memleak

memleak跟踪内存分配和回收请求,收集每次分配的调用栈。 属于bcc套件

/usr/share/bcc/tools/memleak -a -p ${pid}

### pcstat

可以查看某个文件是否被缓存 https://github.com/tobert/pcstat

pcstat /bin/ls

参考 极客时间专栏《Linux 性能优化实战》

原文地址 https://lework.github.io/2019/10/22/mem/

Similar Posts

Comments