内存性能指标
系统内存使用情况:
1 | ~]# free |
2 | total used free shared buff/cache available |
3 | Mem: 2031744 98176 1826192 8784 107376 1800144 |
4 | Swap: 2097148 0 2097148 |
- used: 已用内存
- free: 剩余内存
- shared: 共享内存, 共享内存是通过tmpfs实现的,它的大小就是tmpfs使用的内存大小
- available: 可用内存,是新进程可以使用的最大内存,包括剩余内存和还未使用的内存
- buffer/cache: 缓存包括两部分,一部分是磁盘读取文件的页缓存,用来缓存从磁盘读取的数据,加速以后再次访问速度,另一部分是slab分配的可回收缓存;缓冲区是对原始磁盘的临时存储,用来缓存将要写入磁盘的数据,统一优化磁盘写入。
swap的使用情况:
- 已用空间
- 剩余空间
1 | vmstat 1 1 |
2 | procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- |
3 | r b swpd free buff cache si so bi bo in cs us sy id wa st |
4 | 1 0 0 1815348 2108 111872 0 0 1 0 11 11 0 0 100 0 0 |
- si: 换入,每秒从磁盘读入虚拟内存的大小,这个值大于0,表示物理内存不够或者内存泄漏,需要排查内存问题
- so: 换出,每秒从内存写入磁盘的大小,这个值大于0,表示物理内存不够用,需要排查内存问题。
进程内存使用情况:
1 | ~] # top |
2 | |
3 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND |
4 | 1 root 20 0 128032 7996 5556 S 0.0 0.4 0:01.03 systemd |
5 | 2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd |
- VIRI: 虚拟内存,包括了进程的代码段、数据段、共享内存、已经申请的堆内存和已经换出的内存等,已经申请的内存,即使还未分配物理内存,也算做虚拟内存
- RSS: 常驻内存,是进程实际使用的物理内存,不包括swap和共享内存
- SHR: 共享内存,包括与其他进程共同使用的真实共享内存,包括加载的动态链接库以及程序的代码段
- %MEM: 进程使用物理内存占系统内存的百分比
内存
内存通常被认为的是物理内存,但是只有内核才可以直接访问物理内存,进程需要访问内存,linux内核需要给每个进程都提供一个独立的虚拟地址空间,访问的是虚拟内存。
虚拟内存空间的内部被划分为内核空间和用户空间:
- 进程在用户态,只能访问用户空间内存
- 进程进入内核态才能访问内核空间内存
- 每个进程都包含内核空间,但这些内核空间都关联相同的物理内存
内存映射
将虚拟内存地址映射到物理内存地址,为了完成内存映射,内核每个进程都维护了一张页表,记录虚拟地址和物理地址的映射关系,页表实际存储在CPU的内存管理单元MMU,这样处理器就可以直接通过硬件找出要访问的内存。
页
MMU并不是以字节为单位管理内存,而是页,通常是4KB大小,但是这样会导致一个问题,整个页表变的非常大,为了解决这个页表项过多,Linux提供两种机制:多级页表和大页
- 多级页表
多级页表就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移,Linux用四级页表管理内存页:
PGD: 全局页目录
PUD: 上层页目录
PMD: 中间页目录
PTE: 直接页表
Offset: 页
大页
就是比普通页更大的内存块,常见的大小2MB和1GB, 大页通常用在使用大量内存的进程上
缺页异常
在内存分配的原理中,系统调用内存分配请求后,并不会立刻为其分配物理内存,而是首次请求时,通过缺页异常来分配,缺页异常分为两种场景:
- 可以直接从物理内存中分配时,被称为次缺页异常
- 需要磁盘I/O介入,被称为主缺页异常
系统回收内存
- 回收缓存,比如LRU算法,回收最近使用最少的内存页面
- 回收不常使用的内存,把不常用的内存通过交换分区直接写入到磁盘中
- 杀死进程,内存紧张系统还会通过OOM
OOM是内核的一种保护机制,使用oom_score为每个进程的内存使用情况进行评分
一个进程消耗的内存越大,oom_score就越大
一个进程运行的CPU越多,oom_score就越小
可以通过/proc/{pid}/oom_adj [-17, 15],数值越大,表示数值越大,越容易被oom杀死,-17表示禁止
Buffer/Cache
Buffer是对磁盘数据的缓存,而cache是对文件数据的缓存,既会用在读请求中,也会在写请求中。
- 从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作。
- 从读的角度来说,既可以加速读取那些需要频繁访问的数据,也降低了频繁I/O对磁盘的压力
/proc/<pid>/smaps
- Rss: 实际使用物理内存(包含共享库占用的内存)
- Pss: 实际使用的物理内存(按比例包含共享库占用的内存)
所有如果要计算一个进程实际使用的物理内存:
1 | awk '/Pss:/{sum+=$2}END{print sum}' /proc/<pid>/smaps |
要计算实际使用的物理内存大小:
1 | grep Pss /proc/[1-9]*/smaps | awk '{total+=$2}; END{printf "%d kb\n", total}' |
/proc/sys/vm/drop_caches
- 1 清空页缓存
- 2 清空inode和目录树缓存
- 3 清空所有缓存
内存泄漏
内存泄漏危害极大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用,内存泄漏不断累积,甚至会耗尽系统内存。
memleak是bcc软件包的一个工具,而bcc工具需要内核4.1或者更高,centos需要升级内核:
1 | # 升级系统 |
2 | yum update -y |
3 | # 安装elrepo |
4 | rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org |
5 | rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm |
6 | # 安装新内核 |
7 | yum remove -y kernel-headers kernel-tools kernel-tools-libs |
8 | yum --enablerepo="elrepo-kernel" install -y kernel-ml kernel-ml-devel kernel-ml-headers kernel-ml-tools kernel-ml-tools-libs kernel-ml-tools-libs-devel |
9 | # 更新grub后重启 |
10 | grub2-mkconfig -o /boot/grub2/grub.cfg |
11 | grub2-set-default 0 |
12 | reboot |
1 | 安装bcc |
2 | yum install -y bcc-tools |
3 | export PATH=$PATH:/usr/share/bcc/tools |
memleak:
- -a表示显示每个内存分配请求的大小以及地址
- -p指定案例应用的pid
1 | memleak -a -p <pid> |
SWAP
内存回收
内存回收,也就是系统释放可以回收的内存,比如缓存和缓冲区,就属于可回收内存,在内存管理中,通常被叫做文件页,大部分文件页都是可以直接回收的。
那些被程序修改过,并且暂时还没写入磁盘的数据(脏页)写入磁盘的两种方式:
- 可以在应用程序中,通过系统调用fsync,把脏页同步到磁盘
- 也可以交给系统,由内核线程pdflush负责脏页刷新
应用程序动态分配的堆内存,就是内存管理的匿名页,如果这些内存分配后很少被访问,可以把它们暂时先存在磁盘中,释放内存给其他进程。
swap机制,swap把不常访问的内存先写到磁盘中,然后释放这些内存,给其他进程使用,再次访问这些内存时,重新从磁盘读入内存。
新的内存分配请求,但是剩余内存不足,系统需要回收一部分内存,这个过程通常被称为直接内存回收。
除了直接内存回收,还有内核线程来定期回收内存,kswapd0,kswapd0定义三个内存阈值:
- 页最小阈值
- 页低阈值
- 页高阈值
1 | - page > 页高阈值: 内存充足 |
2 | - 页高阈值 > page > 页低阈值: 内存分配正常 |
3 | - 页低阈值 > page > 页最小阈值: 内存压力巨大 |
4 | - 页最小阈值 > page: 内存基本耗尽,仅内核可分配内存 |
剩余内存小于低阈值,就会触发内存回收
1 | /proc/sys/vm/min_free_kbytes # 页最小阈值 |
2 | |
3 | pages_low = pages_min * 5/4 |
4 | pages_high = pages_min * 3/2 |
- 对文件页的回收,当然就是直接回收缓存,或者把脏页写到磁盘后再回收
- 对匿名页的回收,通过swap机制,把它们写入磁盘后在释放内存
linux提供了一个/proc/sys/vm/swappiness选项,用来调整使用swap的积极程度
swappiness的范围0-100,数值越大越积极使用swap,更倾向于回收匿名页;数值越小,越消极使用swap,也就是更倾向于回收文件页。
即使设置0,剩余内存+文件页 小于 高阈值,还是会发生swap
性能指标和工具的联系
内存指标 | 性能工具 |
---|---|
系统已用、可用、剩余内存 | free vmstat sar /proc/meminfo |
进程虚拟内存、常驻内存、共享内存 | ps top |
进程内存分布 | pmap |
进程swap换出内存 | top /proc/pid/status |
进程缺页异常 | ps top |
系统换页清空 | sar |
缓存/缓冲区用量 | free vmstat sar cachestat |
缓存/缓冲区命中率 | cachetop |
swap已用空间和剩余空间 | free 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 | 查看指定文件的缓存情况 |
内存问题解决思路
为了快速定位内存问题,通常会先运行几个覆盖面比较大的性能工具,比如free、top、vmstat、pidstat
- 先用free或top,查看系统的整体的内存使用情况
- 在用vmstat和pidstat,查看一段时间的趋势,从而判断出内存问题类型
- 在进行详细的分析,比如内存分配分析、缓存/缓冲区分析、具体进程的内存使用分析等
第一阶段:
free
- 系统已用内存
- 系统剩余内存
- 系统可用内存
- 缓存/缓冲区
- swap已用空间
- swap剩余空间
确认系统的整体内存使用情况
第二阶段:
vmstat/sar查看趋势
- 系统剩余内存
- 缓冲区
- 缓存
- swap换入
- swap换出
- 缺页异常
确定内存瓶颈
第三阶段:
确定内存问题
- 系统剩余内存不足
- 系统可用内存不足
- 缓存/缓冲区过多
- 内存泄漏
- 大量缺页异常
- 使用了swap异常
- swap剩余空间过小
第四阶段:
内存分配分析
memleak
strace
valgrind
slabtop
/proc/buddyinfo
进程内存分析
pidstat/top
pmap
/proc/pid/status
/proc/pid/smaps
缓存/缓冲区分析
cachetop
slabtop
cachestat
pcstat
内存优化思路
- 最好禁止swap,如必须开启swap。降低swappiness的值
- 减少内存的动态分配,可以使用内存池、大页等
- 尽量使用缓存和缓冲区来访问数据
- 使用cgroup等方式限制进程的内存使用情况
- 通过/proc/pid/oom_adj,调整核心应用的oom_score