惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
腾讯CDC
D
Docker
G
Google Developers Blog
D
DataBreaches.Net
雷峰网
雷峰网
Blog — PlanetScale
Blog — PlanetScale
S
SegmentFault 最新的问题
The Cloudflare Blog
有赞技术团队
有赞技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Stack Overflow Blog
Stack Overflow Blog
大猫的无限游戏
大猫的无限游戏
量子位
美团技术团队
aimingoo的专栏
aimingoo的专栏
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Engineering at Meta
Engineering at Meta
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 观海云不远

ChatView:把 ChatGPT / Claude 对话保存、分享和发布成独立网页 AI对普通人到底有什么用?这8个场景告诉你答案 用 AI 做点不一样的事:你出想法,我来实现 Python 调试工具PDB的基本使用 帮园子算账省钱 linux I/O性能优化 CPU优化方案 系统中出现大量不可中断进程和僵尸进程怎么办? CPU 使用率过高怎么办 CPU上下文切换 到底应该怎么理解“平均负载”? iOS开发学习笔记(OC语言)——block使用示例 iOS开发学习笔记(OC语言)——调起APP(URL Scheme) iOS开发学习笔记(OC语言)——文件基本操作 iOS开发学习笔记(OC语言)——网络请求 iOS开发学习笔记(OC语言)——底部tab栏 iOS开发学习笔记(OC语言)——线程安全的单例 iOS开发学习笔记(OC语言)——UIView和UIViewController生命周期 Jenkins安装配置回滚 Mac 下反编译Android APK
Linux内存问题排查工具
观海云不远 · 2023-08-07 · via 博客园 - 观海云不远

工具

为了迅速定位内存问题,通常会先运行几个覆盖面比较大的性能工具,比如 free、top、vmstat、pidstat 等。具体的分析思路主要有这几步。

  1. 先用 free 和 top,查看系统整体的内存使用情况。

  2. 再用 vmstat 和 pidstat,查看一段时间的趋势,从而判断出内存问题的类型。

  3. 最后进行详细分析,比如内存分配分析、缓存 / 缓冲区分析、具体进程的内存使用分析等。

内存调优最重要的就是,保证应用程序的热点数据放到内存中,并尽量减少换页和交换。

常见的优化思路有这么几种。

  1. 最好禁止 Swap。如果必须开启 Swap,降低 swappiness 的值,减少内存回收时 Swap 的使用倾向。

  2. 减少内存的动态分配。比如,可以使用内存池、大页(HugePage)等。

  3. 尽量使用缓存和缓冲区来访问数据。比如,可以使用堆栈明确声明内存空间,来存储需要缓存的数据;或者用 Redis 这类的外部缓存组件,优化数据的访问。

  4. 使用 cgroups 等方式限制进程的内存使用情况。这样,可以确保系统内存不会被异常进程耗尽。

  5. 通过 /proc/pid/oom_adj ,调整核心应用的 oom_score。这样,可以保证即使内存紧张,核心应用也不会被 OOM 杀死。

oom_score

OOM(Out of Memory)其实是内核的一种保护机制。它监控进程的内存使用情况,并且使用 oom_score 为每个进程的内存使用情况进行评分:

  • 一个进程消耗的内存越大,oom_score 就越大;

  • 一个进程运行占用的 CPU 越多,oom_score 就越小。

进程的 oom_score 越大,代表消耗的内存越多,也就越容易被 OOM 杀死,从而可以更好保护系统。

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

oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死;数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。

比如用下面的命令,你就可以把 sshd 进程的 oom_adj 调小为 -16,这样, sshd 进程就不容易被 OOM 杀死。

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

free

free可以看系统整体内存使用情况

free

              total        used        free      shared  buff/cache   available
Mem:        3514764      940248      219344       73716     2355172     2219028
Swap:             0           0           0

free 输出的是一个表格,其中的数值都默认以字节为单位。表格总共有两行六列,这两行分别是物理内存 Mem 和交换分区 Swap 的使用情况,而六列中,每列数据的含义分别为:

  • 第一列,total 是总内存大小;

  • 第二列,used 是已使用内存的大小,包含了共享内存;

  • 第三列,free 是未使用内存的大小;

  • 第四列,shared 是共享内存的大小;

  • 第五列,buff/cache 是缓存和缓冲区的大小;

  • 最后一列,available 是新进程可用内存的大小。

这里尤其注意一下,最后一列的可用内存 available 。available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。不过,并不是所有缓存都可以回收,因为有些缓存可能正在使用中。

top

top 可以看进程内存使用情况

top


top - 09:12:28 up 367 days, 14:10,  0 users,  load average: 0.04, 0.02, 0.00
Tasks: 132 total,   1 running,  87 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.7 us,  0.3 sy,  0.0 ni, 98.8 id,  0.0 wa,  0.0 hi,  0.2 si,  0.0 st
KiB Mem :  3514764 total,   368416 free,   943196 used,  2203152 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  2215896 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                          
 1962 ubuntu    20   0  956624  74288  36344 S   0.3  2.1   0:04.76 node                                                                                                                                                             
 2063 ubuntu    20   0  976732  87552  38292 S   0.3  2.5   0:05.73 node                                                                                                                                                             
11399 root      20   0 1115012 150912  19764 S   0.3  4.3 560:46.93 YDService                                                                                                                                                        
30256 root      20   0   64552  11232   3632 S   0.3  0.3  44:22.12 barad_agent                                                                                                                                                      
    1 root      20   0  225544   7596   4920 S   0.0  0.2  19:44.42 systemd                                                                                                                                                          
    2 root      20   0       0      0      0 S   0.0  0.0   0:14.03 kthreadd                                                                                                                                                         
    4 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 kworker/0:0H                                                                                                                                                     
    6 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 mm_percpu_wq                                                                                                                                                     
    7 root      20   0       0      0      0 S   0.0  0.0   3:12.92 ksoftirqd/0                                                                                                                                                      
    8 root      20   0       0      0      0 I   0.0  0.0  35:31.52 rcu_sched                                                                                                                                                        
    9 root      20   0       0      0      0 I   0.0  0.0   0:00.00 rcu_bh                                                                                                                                                           
   10 root      rt   0       0      0      0 S   0.0  0.0   1:06.76 migration/0                                                                                                                                                      
   11 root      rt   0       0      0      0 S   0.0  0.0   0:37.36 watchdog/0                                                                                                                                                       
   12 root      20   0       0      0      0 S   0.0  0.0   0:00.00 cpuhp/0  
  • VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。

  • RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。

  • SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。

  • %MEM 是进程使用物理内存占系统总内存的百分比。

top 输出时,要注意两点。

第一,虚拟内存通常并不会全部分配物理内存。从上面的输出,你可以发现每个进程的虚拟内存都比常驻内存大得多。

第二,共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然,SHR 也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的 SHR 直接相加得出结果。

Buffer & Cache

Buffer 是对磁盘数据的缓存

Cache 是文件数据的缓存

它们既会用在读请求中,也会用在写请求中。

磁盘是一个块设备,可以划分为不同的分区;在分区之上再创建文件系统,挂载到某个目录,之后才可以在这个目录中读写文件。

在读写普通文件时,会经过文件系统,由文件系统负责与磁盘交互;而读写磁盘或者分区时,就会跳过文件系统,也就是所谓的“裸I/O“。这两种读写方式所使用的缓存是不同的,也就是文中所讲的 Cache 和 Buffer 区别。

cachestat & cachetop

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

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

这两个工具都是 bcc 软件包的一部分,需要通过源码的方式进行安装,且LLVM需要11及以上。

cachestat 运行效果:

sudo python3 /usr/share/bcc/tools/cachestat


    HITS   MISSES  DIRTIES HITRATIO   BUFFERS_MB  CACHED_MB
   18102        0       60  100.00%          196       1440
    7126        0       43  100.00%          196       1440
    6883        0       40  100.00%          196       1440
      14        0       13  100.00%          196       1440
    3528        0        9  100.00%          196       1440
      19        0        8  100.00%          196       1440

cachestat 的输出其实是一个表格。每行代表一组数据,而每一列代表不同的缓存统计指标。这些指标从左到右依次表示:

  • HITS ,表示缓存命中的次数;

  • MISSES ,表示缓存未命中的次数;

  • DIRTIES, 表示新增到缓存中的脏页数;

  • BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位;

  • CACHED_MB 表示 Cache 的大小,以 MB 为单位。

cachetop 运行效果

sudo python3 /usr/share/bcc/tools/cachetop

12:14:36 Buffers MB: 315 / Cached MB: 1363 / Sort: HITS / Order: descending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
    1608 ubuntu   ps                    929        0        0     100.0%       0.0%
    1604 root     awk                   909        0        0     100.0%       0.0%
    1610 ubuntu   cpuUsage.sh           762        0        0     100.0%       0.0%
    1615 ubuntu   sed                   642        0        0     100.0%       0.0%
    1611 ubuntu   sed                   624        0        0     100.0%       0.0%
    1603 root     grep                  596        0        0     100.0%       0.0%
    1617 ubuntu   cpuUsage.sh           540        0        0     100.0%       0.0%
    1601 root     barad_agent           529        0        0     100.0%       0.0%
    1613 ubuntu   sleep                 515        0        0     100.0%       0.0%

pcstat

pcstat 可以指定文件在内存中的缓存大小,查看文件在内存中的缓存大小以及缓存比例。

pcstat 是一个基于 Go 语言开发的工具,所以安装它之前,你首先应该安装 Go 语言。安装完 Go 语言,再运行下面的命令安装 pcstat:

export GOPATH=~/go
export PATH=~/go/bin:$PATH
go install github.com/tobert/pcstat@latest

运行效果

pcstat /bin/ls

+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| /bin/ls | 133792         | 33         | 33        | 100.000 |
+---------+----------------+------------+-----------+---------+

memleak

memleak 是 bcc 软件包中的一个工具,我们一开始就装好了,执行 /usr/share/bcc/tools/memleak 就可以运行它。比如,我们运行下面的命令:

/usr/share/bcc/tools/memleak -p $(pidof app) -a

Attaching to pid 12512, Ctrl+C to quit.
[03:00:41] Top 10 stacks with outstanding allocations: 
  addr = 7f8f70863220 size = 8192 
  addr = 7f8f70861210 size = 8192 
  addr = 7f8f7085b1e0 size = 8192 
  addr = 7f8f7085f200 size = 8192 
  addr = 7f8f7085d1f0 size = 8192 
  40960 bytes in 5 allocations from stack 
    fibonacci+0x1f [app] 
    child+0x4f [app] 
    start_thread+0xdb [libpthread-2.27.so]

swap 机制

在内存资源紧张时,Linux 通过直接内存回收和定期扫描的方式,来释放文件页和匿名页,以便把内存分配给更需要的进程使用。

文件页的回收会直接清空,或者把脏数据写回磁盘后再释放。而对匿名页的回收,需要通过 Swap 换出到磁盘中,下次访问时,再从磁盘换入到内存中。

kswapd0 定义了三个内存阈值(watermark,也称为水位),分别是页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high)。

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

  • 剩余内存小于页最小阈值,说明进程可用内存都耗尽了,只有内核才可以分配内存。

  • 剩余内存落在页最小阈值和页低阈值中间,说明内存压力比较大,剩余内存不多了。这时 kswapd0 会执行内存回收,直到剩余内存大于高阈值为止。

  • 剩余内存落在页低阈值和页高阈值中间,说明内存有一定压力,但还可以满足新内存请求。

  • 剩余内存大于页高阈值,说明剩余内存比较多,没有内存压力。

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

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

min_free_kbytes 设置了页最小阈值,而其他两个阈值,都是根据页最小阈值计算生成的,计算方法如下 :

pages_low = pages_min*5/4
pages_high = pages_min*3/2

还可以设置 /proc/sys/vm/swappiness,来调整文件页和匿名页的回收倾向。

swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。虽然 swappiness 的范围是 0-100,不过要注意,这并不是内存的百分比,而是调整 Swap 积极程度的权重,即使你把它设置成 0,当剩余内存 + 文件页小于页高阈值时,还是会发生 Swap。

在 NUMA 架构下,每个 Node 都有自己的本地内存空间,而当本地内存不足时,默认既可以从其他 Node 寻找空闲内存,也可以从本地内存回收。你可以设置 /proc/sys/vm/zone_reclaim_mode ,来调整 NUMA 本地内存的回收策略。

你可以通过 /proc/sys/vm/zone_reclaim_mode 来调整。它支持以下几个选项:默认的 0 ,表示既可以从其他 Node 寻找空闲内存,也可以从本地回收内存。1、2、4 都表示只回收本地内存,2 表示可以回写脏数据回收内存,4 表示可以用 Swap 方式回收内存。