Linux 内核性能优化的全景指南,可都在这里了,强烈推荐收藏~
Linux 性能优化
性能优化
性能指标
高并发和响应快对应着性能优化的两个核心指标:吞吐和延时

从应用负载的角度来看,在用户体验层面带来了显著的影响
性能问题的核心在于系统资源已达到极限;然而在处理速度上仍存在不足,导致无法满足更多请求的需求。其实质则是识别出应用或系统运行中的关键限制因素,进而采取措施规避或减轻这些问题的影响。
- 确定绩效标准以测定程序与系统的执行效率。
- 制定绩效目标要求程序与系统达到预期水平。
- 实施基准测试方案以比较程序系统的实际表现。
- 排查关键问题并识别关键问题所在。
- 部署监控机制并建立告警机制。
根据不同的性能问题需要选择相应的性能分析工具以实现精准的有效性评估和优化目标

到底应该怎么理解”平均负载”
Average Load: 在单位时间区间内, 系统处于可运行状态与不可中断状态的平均进程数量, 即为系统的平均活跃进程数量. 这一指标与我们通常意义上的CPU使用率之间并无直接关联. 其中, 不可中断的状态特指系统对进程中断的一种保护机制, 即当进程中断时将立即停止当前的操作并切换到高优先级处理. 不可中断的过程是指那些正在执行内核态关键流程的任务, 如等待设备I/O操作响应的任务.
平均负载多少时合理
在实际生产环境中实施系统平均负载监控机制,在通过历史数据分析的基础上判断出负载变化趋势的情况下,则需立即展开原因分析。设定阈值标准(例如:当系统平均负载超过CPU总数的70%时),我们可以有效识别潜在问题并采取应对措施。需要注意的是这一指标与CPU使用率概念之间存在明显差异,请结合实际工作场景进行理解与应用
- 高使用率的CPU进程消耗大量CPU资源,这些操作会直接提高系统的平均负载水平,从而与系统性能指标同步提升
- I/O 热的进程中,其I/O操作的等待会提高平均负载水平,但CPU利用率未必显著提升
- 高度依赖CPU调度的任务运行可能会导致较高的系统繁忙程度,与此同时CPU利用率也会呈现显著提升
当平均负载较高时,可能源于CPU密集型过程的影响,也可能由于I/O繁忙的情况导致。在进行具体分析时, 可借助mpstat/pidstat工具来辅助分析负载来源
CPU
文章福利
学习福利

内核资料直通车:[ Linux内核源码技术学习路线+视频教程代码资料

Linux内核源代码技术的学习路径包括视频教学、相关代码资源等
专业学习渠道:[ Linux内核源码|内存调优|文件系统|进程管理|设备驱动|网络协议栈-学习视频教程-腾讯课堂] 有没有发现仅靠操作系统原理知识无法应对实际问题?面对海量Linux内核源代码时是否常常感到无从下手?这门课程将带领您通过理论与实践相结合的方式深入解析核心代码库的关键部分,并全面掌握Linux操作系统运行的基本规律与内在机制。

https://ke.qq.com/course/4032547?flowToken=1044374 "深入解析Linux内核源代码与内存优化技术的学习资源"
CPU上下文切换(上)
CPUcontext切换是指将当前运行的任务中的CPU寄存器与程序计数器(PC)中的信息进行存储,并随后将其更新至新任务对应的寄存器与程序计数器中。接着将当前被中断的任务转移到新的PC值所指定的位置继续执行新的操作。其中被保存下来的上下文信息会被系统内核持续维护,并在后续的任务重新调度执行时及时加载出来;这确保了原有任务的状态得以完整保留。
按照任务类型,CPU 上下文切换分为:
- 进程上下文切换
- 线程上下文切换
- 中断上下文切换
进程上下文切换
Linux进程依据其权限等级划分出内核空间与用户空间。当一个Linux进程从用户态转换至内核态时,则必须借助系统调用来实现这一过程。
一次系统调用过程其实进行了两次 CPU 上下文切换:
- CPU寄存器中的用户态指令位置先被存储下来;随后CPU寄存器的内容被更新为执行内核任务的位置信息,并转入内核态对内核任务进行处理;
- 系统调用完成后,CPU寄存器将恢复之前存储的用户态数据位置,并切换回用户空间继续执行。
系统调用过程中不会使用虚拟内存等进程用户态资源,并且不进行进程中断或状态转换操作。与传统的进程中态context switching方式不同的是,在这种情况下系统调用通常被归类为主权模式context switching行为。
该系统中的进程运行依赖于内核管理和调度机制,在程序状态切换时仅允许在内核态环境下进行操作。为此,在执行程序状态切换前必须先将该进程的虚拟内存信息以及栈的状态进行记录。随后,在切换到新进程时还需更新并刷新新进程中相关的虚拟内存空间以及用户栈的状态信息。
仅当CPU被调度使用时
线程上下文切换
线程上下文切换分为两种:
- 前后线程都属于同一个进程,在更换过程中不占用虚拟内存资源。只需要更换线程的私有数据及寄存器等。
- 前后线程分别位于不同的进程中,其过程交换机制与进程上下文转换一致。
同进程的线程切换消耗资源较少,这也是多线程的优势。
中断上下文切换
中断上下文切换并不涉及程序的用户态(User Mode),因而中断上下文仅包含执行内核态中断服务程序所需的必要状态(如CPU寄存器、内核堆栈及硬件中断参数等)。
因为中断处理的优先顺序高于进程处理,在执行时不会同时进行中断上下文切换与进程上下文切换。
CPU上下文切换(下)
通过 vmstat 可以查看系统总体的上下文切换情况
vmstat 5 #每隔5s输出一组数据
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 103388 145412 511056 0 0 18 60 1 1 2 1 96 0 0
0 0 0 103388 145412 511076 0 0 0 2 450 1176 1 1 99 0 0
0 0 0 103388 145412 511076 0 0 0 8 429 1135 1 1 98 0 0
0 0 0 103388 145412 511076 0 0 0 0 431 1132 1 1 98 0 0
0 0 0 103388 145412 511076 0 0 0 10 467 1195 1 1 98 0 0
1 0 0 103388 145412 511076 0 0 0 2 426 1139 1 0 99 0 0
4 0 0 95184 145412 511108 0 0 0 74 500 1228 4 1 94 0 0
0 0 0 103512 145416 511076 0 0 0 455 723 1573 12 3 83 2 0
- context switches的数量每秒达到...
- 每秒发生的中断事件数量。
- r表示处于运行或可运行状态下的进程数量。
- b表示处于不可阻塞睡眠状态下的进程数量。
为了详细了解每个进程的情况,请确保调用pidstat命令以观察每一个进程的状态变化情况
pidstat -w 5
14时51分16秒 UID PID cswch/s nvcswch/s Command
14时51分21秒 0 1 0.80 0.00 systemd
14时51分21秒 0 6 1.40 0.00 ksoftirqd/0
14时51分21秒 0 9 32.67 0.00 rcu_sched
14时51分21秒 0 11 0.40 0.00 watchdog/0
14时51分21秒 0 32 0.20 0.00 khugepaged
14时51分21秒 0 271 0.20 0.00 jbd2/vda1-8
14时51分21秒 0 1332 0.20 0.00 argusagent
14时51分21秒 0 5265 10.02 0.00 AliSecGuard
14时51分21秒 0 7439 7.82 0.00 kworker/0:2
14时51分21秒 0 7906 0.20 0.00 pidstat
14时51分21秒 0 8346 0.20 0.00 sshd
14时51分21秒 0 20654 9.82 0.00 AliYunDun
14时51分21秒 0 25766 0.20 0.00 kworker/u2:1
14时51分21秒 0 28603 1.00 0.00 python3
- cswch 每秒主动上下文切换次数(因进程无法获取所需资源导致的上下文切换)
- nvcswch 每秒非主动上下文切换次数(基于系统强制调度的时间片轮流)
vmstat 1 1 #新终端观察上下文切换情况
此时发现cs数据明显升高,同时观察其他指标:
r列:远超系统CPU个数,说明存在大量CPU竞争
us和sy列:sy列占比80%,说明CPU主要被内核占用
in列:中断次数明显上升,说明中断处理也是潜在问题
vmstat 1 1 #新终端观察上下文切换情况
当CPU被多个进程占用时(即运行/等待状态),会导致大量的上下文切换操作发生。每一次的上下文切换都会引起资源分配上的重新计算,并因此引发更多的中断事件(即引起频繁的上下文切换)。由于频繁的中断事件会导致处理器处于低效状态(即显著提高系统的CPU使用率),从而进一步加剧了系统资源利用率的问题。
pidstat -w -u 1 #查看到底哪个进程导致的问题
通过观察结果发现sysbench导致CPU使用率过高。然而pidstat输出的相关上下文总数却并不算多。分析sysbench模拟的是线程切换行为因此建议在运行pidstat后附加-t参数以查看线程指标
另外对于中断次数过多,我们可以通过 /proc/interrupts 文件读取
watch -d cat /proc/interrupts
观察到变化频率最高的重调度中断(RES)出现。该中断用于唤醒空闲状态的CPU以调度新的任务运行。研究结果表明主要问题是由于过多的任务被频繁调度导致。
某个应用的CPU使用率达到100%,怎么办?
Linux作为一个多任务操作系统的实现,在处理多任务时将每个CPU执行时间段划分为极小的时间片段,并由调度机制轮流分配资源给各个运行的任务。为了确保足够的CPU资源被有效利用,在启动后会预先设定好预定周期率,并在每个预定周期到来时触发计时中断,并利用全局计时器记录自开机以来经过的预定周期数。每当计时中断发生时,则当前计数值加一。
表示除空闲时段外,在总CPU运行时间内所占比重。通过查阅/proc/stat提供的数据能够得出相应的数值指标。因为/proc/stat记录了自开机以来各时刻经过的所有时钟周期,其结果反映的是自开机以来整个运行过程中的平均利用率;这种整体平均缺乏时效性。每隔一定时间段记录两次数值并求其差值能够得到该段时间内的具体变化情况;性能分析工具给出的结果通常是基于特定时间段的数据统计;因此在分析时需要注意所选时间段长度的影响因素。
CPU使用率可以通过命令top或ps来查询。用于分析进程的CPU问题,则有perf这一工具。它基于其性能事件采样的机制不仅能够分析系统的各种事件及其内核性能;还可以用来深入分析特定的应用程序中的性能问题。
perf top / perf record / perf report (-g 开启调用关系的采样)
sudo docker run --name nginx -p 10000:80 -itd feisky/nginx
sudo docker run --name phpfpm -itd --network container:nginx feisky/php-fpm
ab -c 10 -n 100 http://XXX.XXX.XXX.XXX:10000/ #测试Nginx服务性能
经测试,在当前配置下系统无法持续处理超过一定数量的请求。随后提升测试负载至1万次/秒,并通过监控工具观察各个CPU的工作状态。最终检测到PHP-FPM进程出现异常行为。
接着用perf来分析具体是php-fpm中哪个函数导致该问题。
perf top -g -p XXXX #对某一个php-fpm进程进行分析
察觉到 sqrt 和 add_function 的 CPU 消耗显著。源自于 sqrt 在发布前未清除测试代码块。由于存在一百万次循环。通过移除冗余代码块后观察到 nginx 的负载能力显著增强。
系统的CPU使用率很高,为什么找不到高CPU的应用?
sudo docker run --name nginx -p 10000:80 -itd feisky/nginx:sp
sudo docker run --name phpfpm -itd --network container:nginx feisky/php-fpm:sp
ab -c 100 -n 1000 http://XXX.XXX.XXX.XXX:10000/ #并发100个请求测试
实验结果中每秒请求数依旧不高,我们将并发请求数降为5后,nginx负载能力依旧很低。此时用top和pidstat发现系统CPU使用率过高,但是并没有发现CPU使用率高的进程。出现这种情况一般时我们分析时遗漏的什么信息,重新运行top命令并观察一会。发现就绪队列中处于Running状态的进行过多,超过了我们的并发请求次数5. 再仔细查看进程运行数据,发现nginx和php-fpm都处于sleep状态,真正处于运行的却是几个stress进程。下一步就利用pidstat分析这几个stress进程,发现没有任何输出。用ps aux交叉验证发现依旧不存在该进程。说明不是工具的问题。再top查看发现stress进程的进程号变化了,此时有可能时以下两种原因导致:
- 进程频繁崩溃并重新启动(如程序运行中的异常情况或配置问题),可能导致进程重新启动;
- 内部调用外部脚本或命令(例如其他应用通过exec调用外部程序),通常运行时间短暂且自动终止;难以通过间隔较长时间的日志查看工具来定位问题
可以通过pstree来查找 stress 的父进程,找出调用关系。
pstree | grep stress
我们发现是由PHP-FPM发起的该子进程。通过查看源码可以发现每个请求都会调用一个stress命令来模拟I/O压力。之前使用top工具显示的结果表明CPU使用率有所上升。我们还需进一步分析是否由该stress命令引起。通过在代码中为每个请求添加 verbose=1 参数后可以查看 stress 命令的日志,在中断测试时发现 stress 命令运行过程中由于权限问题导致无法创建相关文件。
目前仍仅是一种推测。随后将借助perf工具展开深入分析。经测试发现,在时段stress期间系统资源消耗异常高。通过修复权限设置并采取相应优化措施可彻底解决问题。
系统中出现大量不可中断进程和僵尸进程怎么办?
进程状态
R Running/Runnable,表示进程在CPU的就绪队列中,正在运行或者等待运行;
D Disk Sleep,不可中断状态睡眠,一般表示进程正在跟硬件交互,并且交互过程中不允许被其他进程中断;
Z Zombie,僵尸进程,表示进程实际上已经结束,但是父进程还没有回收它的资源;
S Interruptible Sleep,可中断睡眠状态,表示进程因为等待某个事件而被系统挂起,当等待事件发生则会被唤醒并进入R状态;
I Idle,空闲状态,用在不可中断睡眠的内核线程上。该状态不会导致平均负载升高;
T Stop/Traced,表示进程处于暂停或跟踪状态(SIGSTOP/SIGCONT, GDB调试);
X Dead,进程已经消亡,不会在top/ps中看到。
对于不可中断的状态而言,在较短时间内通常会终止并可忽略不计。然而,在系统或硬件出现问题的情况下,进程可能会维持较长一段时间处于不可中断的状态,并且当系统中出现较多的不可中断态时,在这种情况下需要特别留意是否出现了I/O性能问题。
僵尸进程一般更容易在多进程中遇到问题。当父进程中断处理不了子进程的状态时, 子进程中就会提前退出, 从而使得该子进程成为了僵尸进程。如果有很多这样的僵尸进程存在, 它们会耗尽所有的PID号, 最终会导致新进程中无法建立。
磁盘O_DIRECT问题
sudo docker run --privileged --name=app -itd feisky/app:iowait
ps aux | grep '/app'
可以看到,在当前系统中有多个应用程序进程在运行(Ss+和D+表示),其中Ss表示会话的领导进程(即当前活跃的会话),而+号则代表处于前台状态的进程组(FG)。每个进程组包含一组相互关联的进程(子进程),这些子进程均属于同一父进程中执行的任务群(TGS)。会话则定义为共享同一个控制台终端的一组或多组进程组(FG)。通过使用top命令观察系统资源状况可得出以下结论:1)系统的平均负载正在逐步上升,并且在1分钟内达到了与CPU数量相当的负载水平;这表明系统可能已经接近性能瓶颈;2)僵尸进程的数量持续增加;3)虽然短时间使用率较低(us和sys CPU使用率不高),但I/O等待时间却较高;4)所有进程中CPU使用率都不高(处于轻载状态),但有两个进程中处于D状态(等待I/O操作完成)。基于现有数据结果可以看出:I/O等待时间过高导致系统的平均负载持续上升;而僵尸进程数量不断增加则反映出程序未能及时清理子进程中残留的资源。为了深入分析这一问题状况,请参考以下工具分析结果:dstat命令——该命令不仅能够显示CPU使用情况还能展示I/O操作的状态信息;因此它为全面评估系统的资源利用情况提供了有效手段
dstat 1 10 #间隔1秒输出10组数据
能够观察到的是,在wai值上升的过程中会引发大量的磁盘读请求。具体来说,则是由于iowait的变化与其影响因素之间的关系。
之前通过 Top 工具获取了处于 D 状态下的某些进程编号,并利用命令 pidstat -d -p XXX 来显示相关进程的各项 I/O 数据统计信息。经过检查发现,在 D 状态下的所有进程中均未执行任何读写操作。进一步使用 pidstat -d 命令来查看所有进程中各项 I/O 统计数据后发现:观察到 app 进程正在进行磁盘读取操作,并且其磁盘读取速度达到了每秒 32MB 的水平。由于程序访问磁盘必须通过进入内核态的方式实现这一功能:下一步的重点任务就是识别并定位到 app 进程所使用的各项系统调用。
sudo strace -p XXX #对app进程调用进行跟踪
缺少权限信息因此,在这种情况下首先要确认该进程中态是否正常运行。通过ps命令可以发现该进程中态处于Z态( zombie process 状态),即僵尸进程状态。此时使用类似top的工具可能无法提供更多信息。参考第5章的相关内容:使用 perf record -d 和 perf report 进行分析以查看app进程调用栈结构的变化情况。经过详细分析后,“root cause 是 app内部进行了磁盘的直接I/O。”定位到具体代码位置并进行优化即可解决这个问题。
僵尸进程
上述经过优化的iowait值明显减少然而僵尸进程中增加的趋势依然存在为此需要先确定僵尸进程的父进程通过运行pstree -aps XXX命令生成该僵尸进程的调用树图发现其父进程正是app进程中查看app的相关代码需检查子进程中结束时处理是否正确例如是否正确调用了wait或waitpid函数以及是否有注册SIGCHILD信号处理函数等当出现iowait值上升的情况时建议首先使用dstat和pidstat等工具排查是否存在磁盘I/O相关的问题进而排查具体是哪些子进程中出现了导致I/O性能下降的情况此时不能直接依赖strace命令来进行分析可以通过perf工具对相应进程中执行的具体操作进行详细分析对于处理僵尸过程问题的方法主要是通过pstree命令找到其父进程然后对照源代码检查子进程中结束时处理逻辑是否存在漏洞即可
CPU性能指标
- CPU占用率
- 包括处于用户态和低优先级用户态的系统资源利用率较高表明应用程序运行较为繁忙。
- 系统资源利用率较高表明内核运行较为繁忙。
- 系统I/O等待时间较长则表明系统的资源利用率较低。
- 当中断频率较高时表明系统活动较为频繁。
- steal CPU / guest CPU 表示虚拟机占用的资源比例
性能工具
- 平均负载案例
- 先用uptime查看系统平均负载
- 判断负载在升高后再用mpstat和pidstat分别查看每个CPU和每个进程CPU使用情况.找出导致平均负载较高的进程.
- 上下文切换案例
- 先用vmstat查看系统上下文切换和中断次数
- 再用pidstat观察进程的自愿和非自愿上下文切换情况
- 最后通过pidstat观察线程的上下文切换情况
- 进程CPU使用率高案例
- 先用top查看系统和进程的CPU使用情况,定位到进程
- 再用perf top观察进程调用链,定位到具体函数
- 系统CPU使用率高案例
- 先用top查看系统和进程的CPU使用情况,top/pidstat都无法找到CPU使用率高的进程
- 重新审视top输出
- 从CPU使用率不高,但是处于Running状态的进程入手
- perf record/report发现短时进程导致 (execsnoop工具)
- 不可中断和僵尸进程案例
- 先用top观察iowait升高,发现大量不可中断和僵尸进程
- strace无法跟踪进程系统调用
- perf分析调用链发现根源来自磁盘直接I/O
- 软中断案例
- top观察系统软中断CPU使用率高
- 查看/proc/softirqs找到变化速率较快的几种软中断
- sar命令发现是网络小包问题
- tcpdump找出网络帧的类型和来源,确定SYN FLOOD攻击导致
根据不同的性能指标来找合适的工具:

建议首先启动一组具有较高指标收集能力的工具(例如 top、vmstat 和 pidstat 这些工具),通过分析这些工具的结果信息(例如 top/vmstat/pidstat 的输出),可以初步判断属于哪一类性能问题(例如响应时间异常或资源耗尽等)。一旦确定了进程信息(例如通过 kill 命令捕获相关进程),则可以通过 strace 和 perf 工具进一步探究其行为模式(例如函数调用频率或内存使用情况)。如果检测到的是软件中断,则可能需要参考 /proc/softirqs 索引以获取相关信息。

CPU优化
-
应用程序优化
- 编译器优化:编译阶段开启优化选项,如gcc -O2
- 算法优化
- 异步处理:避免程序因为等待某个资源而一直阻塞,提升程序的并发处理能力。(将轮询替换为事件通知)
- 多线程代替多进程:减少上下文切换成本
- 善用缓存:加快程序处理速度
-
系统优化
- CPU绑定:将进程绑定要1个/多个CPU上,提高CPU缓存命中率,减少CPU调度带来的上下文切换
- CPU独占:CPU亲和性机制来分配进程
- 优先级调整:使用nice适当降低非核心应用的优先级
- 为进程设置资源显示: cgroups设置使用上限,防止由某个应用自身问题耗尽系统资源
- NUMA优化: CPU尽可能访问本地内存
- 中断负载均衡: irpbalance,将中断处理过程自动负载均衡到各个CPU上
-
TPS、QPS、系统吞吐量的区别和理解
- QPS(TPS)
- 并发数
- 响应时间
- QPS(TPS)=并发数/平均相应时间
- 用户请求服务器
- 服务器内部处理
- 服务器返回给客户
QPS 类似 TPS,但是对于一个页面的访问形成一个 TPS,但是一次页面请求可能包含多次对服务器的请求,可能计入多次 QPS
-
QPS(Queries Per Second)每秒查询数,在单位时间内一台服务器能够处理的最大查询量。
-
TPS(Transactions Per Second)每秒完成的事务数量,在软件系统运行过程中可同时支持的最大并发操作数。
- 系统吞吐量涉及多个关键指标:
包括:- 每秒查询数
- 每秒事务数量
- 资源利用率
- 响应时间
- 错误率等关键性能指标。
- 系统吞吐量涉及多个关键指标:
内存
Linux内存是怎么工作的
内存映射
通常情况下,普通计算机的主存为动态随机存取存储器(DRAM),只有内核可以直接访问物理记忆体. Linux内核为每个作业程提供一个独立且连续的虚拟地址区,因此,作业程可便捷地使用虚拟记忆体. 虚拟地址区内部划分为虚拟核心区与虚拟用户区两部分;不同长度处理器所占据的空间范围也各不相同. 32位处理器系统中,虚拟核心区占用1G,而虚拟用户区则占用了3G. 64位处理器系统中,虚拟核心区与虚拟用户区均为128T,分别占据了最顶端与最低端的空间区域,而中间部分则属于未定义区域. 并非所有占用过的虚拟记忆体都会有对应的物理记忆体分配;仅当实际被使用的才会如此. 分配完成后,物理记忆体会通过映射机制进行管理. 为了实现高效的地址转换功能,在处理每个作业程时,默认情况下都会维护一个页表记录其对应的物理地址信息.
虚拟内存空间分布
用户空间内存从低到高是五种不同的内存段:
- Read-only segment 包含代码块及常量信息
- Data segment 包含全局变量块及引用信息
- Heap 是动态内存分配区域,并从较低地址向较高地址顺序延伸
- File mapping 是动态链接库与共享内存管理区域,并从较高地址向较低地址顺序收缩
- Stack 包括局部变量块、函数调用记录以及相关上下文信息. 其大小通常固定. 一般约为8MB。
内存分配与回收
分配
malloc 对应到系统调用上有两种实现方式:
- brk() 专为小块内存(<128K)设计,在调整堆顶元素位置时完成内存分配。
在内存被释放后,并未立即归还给可用空间池;相反地,在某些条件下会被缓存以备后续使用。
该函数*mmap()*专门用于处理大于128KB的大块内存,并通过内存映射机制在文件映射区域寻觅可用的空闲内存进行分配
前者的缓存有助于降低缺页异常的发生,并提升内存访问效率。因内存未归还系统,在某些情况下频繁进行内存分配与释放可能导致出现内存量外碎片
后者在释放时直接归还系统,所以每次mmap都会发生缺页异常。
当内存处于高度忙碌状态时,在频繁地进行内存分配操作的情况下,会产生大量缺页故障,并直接引发显著加重内核的管理负担。
上述两种程序/函数并未真正完成内存的分配;这些内存仅在首次访问时会触发缺页异常并进入内核进行管理;随后由内核负责完成内存的分配
回收
内存紧张时,系统通过以下方式来回收内存:
- 回收缓存:该机制采用LRU算法回滚最近最少使用的内存页面;
- 回收不常访问内存:该机制将不常用内存通过交换分区写入磁盘;
- 杀死进程:该机制采用OOM内核保护机制管理进程资源(当进程内存使用率越高时oom_score越大;同时CPU使用率越高时oom_score越低;用户可通过/proc命令手动调节oom_adj参数)
echo -16 > /proc/$(pidof XXX)/oom_adj
如何查看内存使用情况
free来查看整个系统的内存使用情况
top/ps来查看某个进程的内存使用情况
- VIRT 过程所使用的虚拟内存量
- RES 该过程固定 Resident 区域占用的空间量
- SHR 该过程所使用的共享分区空间量
- %MEM 表示该过程所占用物理内存量与系统总容量的比例
怎样理解内存中的Buffer和Cache?
buffer是用于缓存磁盘数据的一种机制,而cache则是用于缓存文件数据的机制;这些机制不仅用于处理读取请求还涉及处理写入请求的过程
如何利用系统缓存优化程序的运行效率
缓存命中率
缓存命中率是衡量系统在响应数据请求时直接调用缓存的概率指标。当缓存命中率越高时,系统因使用缓存而获得的优势越大,从而整体应用性能提升也越显著。安装bcc包后可借助cachestat和cachetop等工具对缓存系统的读写命中情况进行实时监控。通过安装pcstat工具,则能够查看特定文件在内存中的占用大小及其所占的比例。
#首先安装Go
export GOPATH=~/go
export PATH=~/go/bin:$PATH
go get golang.org/x/sys/unix
go ge github.com/tobert/pcstat/pcstat
dd缓存加速
dd if=/dev/sda1 of=file bs=1M count=512 #生产一个512MB的临时文件
echo 3 > /proc/sys/vm/drop_caches #清理缓存
pcstat file #确定刚才生成文件不在系统缓存中,此时cached和percent都是0
cachetop 5
dd if=file of=/dev/null bs=1M #测试文件读取速度
#此时文件读取性能为30+MB/s,查看cachetop结果发现并不是所有的读都落在磁盘上,读缓存命中率只有50%。
dd if=file of=/dev/null bs=1M #重复上述读文件测试
#此时文件读取性能为4+GB/s,读缓存命中率为100%
pcstat file #查看文件file的缓存情况,100%全部缓存
O_DIRECT选项绕过系统缓存
cachetop 5
sudo docker run --privileged --name=app -itd feisky/app:io-direct
sudo docker logs app #确认案例启动成功
#实验结果表明每读32MB数据都要花0.9s,且cachetop输出中显示1024次缓存全部命中
但是直觉上认为如果缓存命中导致读速度不应该这么慢。已知读取次数为1024次且页框大小设置为4KB,在五秒内完成了1024乘以4KB的数据读取(换算下来每秒仅达到0.8MB的速率),与预期的32MB结果相比差距明显。这表明此案例未能充分利用缓存机制,并且怀疑系统调用设置了直接I/O标志从而绕过系统缓存机制。从而引导我们进一步调查系统调用行为
strace -p $(pgrep app)
#strace 结果可以看到openat打开磁盘分区/dev/sdb1,传入参数为O_RDONLY|O_DIRECT
这揭示了为何在实际操作中读取32MB的数据会显得特别缓慢。显然,在缓存机制能够显著提升数据访问速度的情况下,直接从磁盘进行读写操作的速度要慢得多。在深入分析问题后,我们查看了相关案例的源代码,并发现flags字段中明确设置了直接IO标志。移除该选项后重新运行测试用例,并观察性能指标的变化情况。
内存泄漏,如何定位和处理?
在应用程序中,动态内存分配与回收机制是一个关键且繁琐的关键逻辑功能模块。在内存管理过程中会遭遇各种各样的问题:
未能完全回收分配的内存而导致内存泄漏。
拿到的是超出已分配内存边界的地址从而导致程序异常退出。
内存的分配与回收
虚拟内存分布由低到高依次是只读段、数据段、堆、内存映射段和栈五个部分。其中会引起内存泄漏的是:
- 堆:应用软件自行负责其分配与管理;只有当程序退出时才会不再由堆自行负责释放。
- 地址空间映射区:涵盖动态链接库以及用于进程间通信的共享内存;其中的共享内存可由进程自动实现动态分配与管理。
内存泄漏的影响极为显著;那些未及时释放的内存量不仅会使应用程序无法访问它们,在操作系统层面也无法将其重新分配给其他应用;此外,在操作系统的层面同样无法将这些未被回收的内存量重新分配给其他程序使用;随着时间推移以及积累效应逐渐显现的过程中可能会耗尽整个系统的可用内存量。
如何检测内存泄漏
预先安装systat,docker,bcc
sudo docker run --name=app -itd feisky/app:mem-leak
sudo docker logs app
vmstat 3
可以看到,在一段时间内free值持续减少;而buffer和 cache这两个缓存结构的变化幅度则相对较小。通过这一观察结果可以看出,在整个运行过程中系统的内存一致性有所提升;然而这并不意味着系统存在内存泄漏问题;相反地,在这种情况下建议使用memleak工具对系统或相关进程的内存分配与释放情况进行监控
/usr/share/bcc/tools/memleak -a -p $(pidof app)
通过 memleak 分析工具可以看出,在 continuously memory allocation 中并未完成回收操作。调用记录显示 fib 函数申请的 memory 尚未完成 release process. 定位至 source code 后深入分析以 repair potential memory leak issue.
为什么系统的 Swap 变高
当系统内存资源出现紧张情况时
- 缓存区属于可回收资源,在文件管理领域通常被定义为"文件页"。
- 在应用程序层面使用fsync指令实现对"未完成写的页面"进行同步操作。
- 由操作系统处理这些"未完成写的页面"刷新任务。
- 经过修改但尚未保存至内存的数据块(即所谓的"脏页"),必须先将其保存到磁盘后再释放到内存空间。
- 通过内存映射获取的文件映射区域也能够被释放掉;当再次访问该区域时,Aiding next access by reading the file again.
对于系统自动管理的动态分区内存(即我们在内存管理中使用的匿名页),这些内存无法直接释放回可用空间。然而,在Linux系统中,默认情况下会采用Swap机制将不常被访问的内存量暂时存储在磁盘上以释放内存量。当需要使用这些内存量时,在适当的时候会将它们从磁盘读取回到 内存中使用。
Swap原理
其本质是将一块磁盘空间或本地文件模拟为内存进行操作,并通过交换机制实现资源管理。
- 换出:实现暂时不用的内存数据被写入磁盘文件,并回收或删除这些内存区域。
- 换入:当进程重新访问内存时,从磁盘文件中读取并加载相关数据至内存空间中。
Linux如何衡量内存资源是否紧张?
直接进行新的较大规模的内存分配请求时
-
kswapd0 内核级线程定期进行内存回收操作。
为了衡量内存使用情况,我们定义了pages_min、pages_low和pages_high三个阈值,并基于这些指标进行内存的回收操作。 -
当剩余可用内存量低于\texttt{pages\_min}时,则进程已无可用空间。
-
在\texttt{pages\_min}至\texttt{pages\_low}范围内运行时(即\texttt{remaining\_memory}处于该区间),系统面临较大的资源压力。此时kswapd0负责执行内存量回收操作,并将不断进行下去直至恢复到高于\texttt{pages\_high}的状态。
-
当运行状态位于\texttt{pages\_low}与\texttt{pages\_high}之间时(即\texttt{remaining\_memory}处于此区间),系统仍能维持基本的操作能力,并能够处理新增的内存量。
-
当运行状态超出\texttt{.pages\_high}水平时(即\texttt.remaining\_memory}高于\texttt.pages_{high}),此时系统并未出现明显的性能瓶颈。
其中:
\texttt(pages\_low} = \frac{5}{4} \times \texttt(pages\_min}
\texttt(pages_{high}} = \frac{\sqrt[3]{3}}{\sqrt[4]{2}} \times \texttt(pages_{min}}
NUMA 与 SWAP
常见情况下系统剩余内存较多,但Swap存储空间仍然持续增长,这归因于处理器的NUMA架构.在NUMA架构下将多个处理器分配至不同的Node上,每个Node独立拥有本地内存空间.意味着对每个Node的内存使用情况需分别进行评估.
numactl --hardware #查看处理器在Node的分布情况,以及每个Node的内存使用情况
通过查询 /proc/zoneinfo 文件可以得知内存中的三个阈值参数。其中不仅记录了活跃状态下的匿名页面数量以及非活跃状态下的匿名文件数量。当某个计算节点出现内存不足的情况时,在资源调度机制下系统会自动尝试从相邻节点获取空闲资源,并在必要时回滚本地过快增长的使用情况。配置参数可以通过 /proc/sys/vm/zone_raclaim_mode 来设置。
- 0则允许同时从其他节点获取空闲资源以及本地内存的回收
- 1、2、4仅负责本地内存的回收
- 2还可以处理会回脏数据的情况
- 4则可以通过Swap的方式来实现内存回收。
swappiness
在实际回收过程中,在Linux系统中依据 /proc/sys/vm/swapiness 选项来调节Swap应用的程度,在一个介于0到100之间的数值范围内进行设置;当该值较高时,则表明系统会更加活跃地利用Swap空间以回收匿名页;反之则趋向于回收文件页;值得注意的是即便将该参数设为最小值依然会在剩余内存与文件页总量低于页面高度阈值时触发Swap操作
Swap升高时如何定位分析
free #首先通过free查看swap使用情况,若swap=0表示未配置Swap
#先创建并开启swap
fallocate -l 8G /mnt/swapfile
chmod 600 /mnt/swapfile
mkswap /mnt/swapfile
swapon /mnt/swapfile
free #再次执行free确保Swap配置成功
dd if=/dev/sda1 of=/dev/null bs=1G count=2048 #模拟大文件读取
sar -r -S 1 #查看内存各个指标变化 -r内存 -S swap
#根据结果可以看出,%memused在不断增长,剩余内存kbmemfress不断减少,缓冲区kbbuffers不断增大,由此可知剩余内存不断分配给了缓冲区
#一段时间之后,剩余内存很小,而缓冲区占用了大部分内存。此时Swap使用之间增大,缓冲区和剩余内存只在小范围波动
停下sar命令
cachetop5 #观察缓存
#可以看到dd进程读写只有50%的命中率,未命中数为4w+页,说明正式dd进程导致缓冲区使用升高
watch -d grep -A 15 ‘Normal’ /proc/zoneinfo #观察内存指标变化
#发现升级内存在一个小范围不停的波动,低于页低阈值时会突然增大到一个大于页高阈值的值
指出剩余内存与缓冲区的变化趋势是由内存回收与缓存重新分配不断循环导致的结果。有时Swap占用较多资源时会导致缓冲区波动增大。当前查看swippiess值达到60,则属于一种较为平衡的状态。系统将根据实际运行情况选择合适的回收策略。
如何“快准狠”找到系统内存存在的问题
内存性能指标
系统运行中的核心资源管理参数。
已经占用的系统资源与其未被占用的部分。
系统中支持共享使用的临时存储区域(特别适用于tmpfs文件系统实现)。
可用存储空间不仅限于剩余空间还包括可回收缓冲区。
存储在磁盘上的文件片段作为数据存储单位。
缓存机制将这些片段划分为固定大小的块以提高访问效率。
缓冲区是暂时存放未经处理或等待操作的数据块区域。
进程内存指标
虚拟内存:五个主要部分
常驻内存:进程实际占用的物理内存空间,并不包括Swap以及共享内存
共享内存:不仅包括与其他进程共享的内存资源,还包含动态链接库与程序代码段等
Swap 内存:采用Swap交换到磁盘存储的内存空间
缺页异常
- 直接可以从物理内存中分配资源。
- 出现次级缺页异常。
- 需要通过磁盘IO操作(例如Swap)处理主级缺页异常。
- 此时内存访问速度会显著下降。
内存性能工具
根据不同的性能指标来找合适的工具:

内存分析工具包含的性能指标:

如何迅速分析内存的性能瓶颈
通常先运行几个覆盖面比较大的性能工具,如 free,top,vmstat,pidstat 等
- 首先调用free和top命令来观察系统的总体内存占用情况。
- 接着利用vmstat和pidstat工具追踪一段时间内的内存变化趋势。
- 最后对内存使用情况进行详细剖析。
常见的优化思路:
- 尽管Swap可能带来性能问题, 但在必要时也应控制swapiness参数, 尽量维持其最低水平
- 动态内存分配应尽量减少数量, 可以采用内存在线性表中的分段机制或者使用内存在块中的合并机制等方法优化资源利用率
- 数据访问应当优先考虑缓存机制, 可以通过显式声明内存空间区域来管理数据缓存, 或者采用Redis外部缓存组件来优化数据访问模式
- 使用cgroups等技术手段限制进程对内存的需求, 防止异常进程过度占用系统资源以保障整体运行稳定性
- 应当监控核心应用的oom_score参数设置, 并通过/proc/pid/oom_adj提供反馈机制, 在 Mem紧张时及时采取措施避免OOM错误发生
vmstat 使用详解
vmstat 命令是广泛使用的 Linux/Unix 系统监控工具。能够显示指定时间段内服务器的各种状态参数。涵盖服务器 CPU 使用率、内存使用量以及虚拟内存交换等数据。被观察到整个机器的整体资源利用情况,并不仅仅是追踪各个进程的具体资源消耗(应用场景不同)。
vmstat 2
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 1379064 282244 11537528 0 0 3 104 0 0 3 0 97 0 0
0 0 0 1372716 282244 11537544 0 0 0 24 4893 8947 1 0 98 0 0
0 0 0 1373404 282248 11537544 0 0 0 96 5105 9278 2 0 98 0 0
0 0 0 1374168 282248 11537556 0 0 0 0 5001 9208 1 0 99 0 0
0 0 0 1376948 282248 11537564 0 0 0 80 5176 9388 2 0 98 0 0
0 0 0 1379356 282256 11537580 0 0 0 202 5474 9519 2 0 98 0 0
1 0 0 1368376 282256 11543696 0 0 0 0 5894 8940 12 0 88 0 0
1 0 0 1371936 282256 11539240 0 0 0 10554 6176 9481 14 1 85 1 0
1 0 0 1366184 282260 11542292 0 0 0 7456 6102 9983 7 1 91 0 0
1 0 0 1353040 282260 11556176 0 0 0 16924 7233 9578 18 1 80 1 0
0 0 0 1359432 282260 11549124 0 0 0 12576 5495 9271 7 0 92 1 0
0 0 0 1361744 282264 11549132 0 0 0 58 8606 15079 4 2 95 0 0
1 0 0 1367120 282264 11549140 0 0 0 2 5716 9205 8 0 92 0 0
0 0 0 1346580 282264 11562644 0 0 0 70 6416 9944 12 0 88 0 0
0 0 0 1359164 282264 11550108 0 0 0 2922 4941 8969 3 0 97 0 0
1 0 0 1353992 282264 11557044 0 0 0 0 6023 8917 15 0 84 0 0
# 结果说明
- r 表示运行队列(就是说多少个进程真的分配到CPU),我测试的服务器目前CPU比较空闲,没什么程序在跑,当这个值超过了CPU数目,就会出现CPU瓶颈了。这个也和top的负载有关系,一般负载超过了3就比较高,超过了5就高,超过了10就不正常了,服务器的状态很危险。top的负载类似每秒的运行队列。如果运行队列过大,表示你的CPU很繁忙,一般会造成CPU使用率很高。
- b 表示阻塞的进程,这个不多说,进程阻塞,大家懂的。
- swpd 虚拟内存已使用的大小,如果大于0,表示你的机器物理内存不足了,如果不是程序内存泄露的原因,那么你该升级内存了或者把耗内存的任务迁移到其他机器。
- free 空闲的物理内存的大小,我的机器内存总共8G,剩余3415M。
- buff Linux/Unix系统是用来存储,目录里面有什么内容,权限等的缓存,我本机大概占用300多M
- cache cache直接用来记忆我们打开的文件,给文件做缓冲,我本机大概占用300多M(这里是Linux/Unix的聪明之处,把空闲的物理内存的一部分拿来做文件和目录的缓存,是为了提高 程序执行的性能,当程序使用内存时,buffer/cached会很快地被使用。)
- si 每秒从磁盘读入虚拟内存的大小,如果这个值大于0,表示物理内存不够用或者内存泄露了,要查找耗内存进程解决掉。我的机器内存充裕,一切正常。
- so 每秒虚拟内存写入磁盘的大小,如果这个值大于0,同上。
- bi 块设备每秒接收的块数量,这里的块设备是指系统上所有的磁盘和其他块设备,默认块大小是1024byte,我本机上没什么IO操作,所以一直是0,但是我曾在处理拷贝大量数据(2-3T)的机器上看过可以达到140000/s,磁盘写入速度差不多140M每秒
- bo 块设备每秒发送的块数量,例如我们读取文件,bo就要大于0。bi和bo一般都要接近0,不然就是IO过于频繁,需要调整。
- in 每秒CPU的中断次数,包括时间中断
- cs 每秒上下文切换次数,例如我们调用系统函数,就要进行上下文切换,线程的切换,也要进程上下文切换,这个值要越小越好,太大了,要考虑调低线程或者进程的数目,例如在apache和nginx这种web服务器中,我们一般做性能测试时会进行几千并发甚至几万并发的测试,选择web服务器的进程可以由进程或者线程的峰值一直下调,压测,直到cs到一个比较小的值,这个进程和线程数就是比较合适的值了。系统调用也是,每次调用系统函数,我们的代码就会进入内核空间,导致上下文切换,这个是很耗资源,也要尽量避免频繁调用系统函数。上下文切换次数过多表示你的CPU大部分浪费在上下文切换,导致CPU干正经事的时间少了,CPU没有充分利用,是不可取的。
- us 用户CPU时间,我曾经在一个做加密解密很频繁的服务器上,可以看到us接近100,r运行队列达到80(机器在做压力测试,性能表现不佳)。
- sy 系统CPU时间,如果太高,表示系统调用时间长,例如是IO操作频繁。
- id 空闲CPU时间,一般来说,id + us + sy = 100,一般我认为id是空闲CPU使用率,us是用户CPU使用率,sy是系统CPU使用率。
- wt 等待IO CPU时间
pidstat 使用详解
该工具主要用于观察或评估所有或特定进程对系统资源的影响情况。它涵盖的内容包括但不限于CPU负载、内存管理效率、设备I/O操作繁忙程度以及进程切换频率等方面。
使用方法:
- pidstat –d interval times 分析各进程IO使用情况
- pidstat –u interval times 获取各进程CPU运行数据
- pidstat –r interval times 显示各进程中存占用情况
- pidstat -w interval times 记录各进程中文切换次数
- p PID 指定特定程序ID
1、统计 IO 使用情况
pidstat -d 1 10
03:02:02 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
03:02:03 PM 0 816 0.00 918.81 0.00 jbd2/vda1-8
03:02:03 PM 0 1007 0.00 3.96 0.00 AliYunDun
03:02:03 PM 997 7326 0.00 1904.95 918.81 java
03:02:03 PM 997 8539 0.00 3.96 0.00 java
03:02:03 PM 0 16066 0.00 35.64 0.00 cmagent
03:02:03 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
03:02:04 PM 0 816 0.00 1924.00 0.00 jbd2/vda1-8
03:02:04 PM 997 7326 0.00 11156.00 1888.00 java
03:02:04 PM 997 8539 0.00 4.00 0.00 java
以下是按照给定规则对原文的改写
2、统计 CPU 使用情况
# 统计CPU
pidstat -u 1 10
03:03:33 PM UID PID %usr %system %guest %CPU CPU Command
03:03:34 PM 0 2321 3.96 0.00 0.00 3.96 0 ansible
03:03:34 PM 0 7110 0.00 0.99 0.00 0.99 4 pidstat
03:03:34 PM 997 8539 0.99 0.00 0.00 0.99 5 java
03:03:34 PM 984 15517 0.99 0.00 0.00 0.99 5 java
03:03:34 PM 0 24406 0.99 0.00 0.00 0.99 5 java
03:03:34 PM 0 32158 3.96 0.00 0.00 3.96 2 ansible
- 用户模式中的进程
- 进程ID
- 占用户空间CPU使用率
- 占系统态CPU使用率
- 占虚拟机态CPU使用率
- 等待运行的占比
- 占用总CPU使用率
- 处理进程所使用的CPU编号
- 执行命令名称
3、统计内存使用情况
# 统计内存
pidstat -r 1 10
Average: UID PID minflt/s majflt/s VSZ RSS %MEM Command
Average: 0 1 0.20 0.00 191256 3064 0.01 systemd
Average: 0 1007 1.30 0.00 143256 22720 0.07 AliYunDun
Average: 0 6642 0.10 0.00 6301904 107680 0.33 java
Average: 997 7326 10.89 0.00 13468904 8395848 26.04 java
Average: 0 7795 348.15 0.00 108376 1233 0.00 pidstat
Average: 997 8539 0.50 0.00 8242256 2062228 6.40 java
Average: 987 9518 0.20 0.00 6300944 1242924 3.85 java
Average: 0 10280 3.70 0.00 807372 8344 0.03 aliyun-service
Average: 984 15517 0.40 0.00 6386464 1464572 4.54 java
Average: 0 16066 236.46 0.00 2678332 71020 0.22 cmagent
Average: 995 20955 0.30 0.00 6312520 1408040 4.37 java
Average: 995 20956 0.20 0.00 6093764 1505028 4.67 java
Average: 0 23936 0.10 0.00 5302416 110804 0.34 java
Average: 0 24406 0.70 0.00 10211672 2361304 7.32 java
Average: 0 26870 1.40 0.00 1470212 36084 0.11 promtail
- UID
- PID
- 每秒发生的小型缺页错误次数(minor page faults),指虚拟内存地址映射至物理内存地址时产生的缺页事件数量
- 每秒发生的主要缺页错误次数(major page faults),指虚拟内存地址映射至物理内存地址时对应页码落在swap分区中情况出现次数
- 虚拟内存使用总量(VSZ virtual memory usage),表示进程占用的虚拟存储空间大小(单位为KB)
- 物理内存占用量(RSS),表示进程实际占用的物理存储空间大小(单位为KB)
- 内存使用率(%MEM),反映当前运行进程对系统总内存资源的占用程度
- 命令(Command),表示该进程所执行的任务名称(task name)
4、查看具体进程使用情况
pidstat -T ALL -r -p 20955 1 10
03:12:16 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command
03:12:17 PM 995 20955 0.00 0.00 6312520 1408040 4.37 java
03:12:16 PM UID PID minflt-nr majflt-nr Command
03:12:17 PM 995 20955 0 0 java
