在日常运维中,"机器很卡"是最常见也最模糊的问题描述。卡顿可能来自 CPU、内存、磁盘 IO、网络中的任何一个环节,也可能是特定场景下的局部问题。本文从系统整体卡顿、打字延迟、内存泄漏三个层面,给出一套可执行的排查方法论。
目录
[1.1 一键快速定位](#1.1 一键快速定位)
[1.2 分维度排查命令](#1.2 分维度排查命令)
[1.3 判断逻辑](#1.3 判断逻辑)
[1.4 最常用的三板斧](#1.4 最常用的三板斧)
[2.1 SSH 远程打字卡](#2.1 SSH 远程打字卡)
[SSH 服务端 DNS 反向解析](#SSH 服务端 DNS 反向解析)
[MTU 不匹配](#MTU 不匹配)
[2.2 本地终端打字卡](#2.2 本地终端打字卡)
[Shell 配置过重](#Shell 配置过重)
[2.3 编辑器中打字卡](#2.3 编辑器中打字卡)
[2.4 快速定位法](#2.4 快速定位法)
[3.1 什么是内存泄漏](#3.1 什么是内存泄漏)
[3.2 为什么要关注内存泄漏](#3.2 为什么要关注内存泄漏)
[3.3 常见内存泄漏场景](#3.3 常见内存泄漏场景)
[3.4 排查方法](#3.4 排查方法)
一、系统卡顿排查框架
系统卡顿的本质是资源瓶颈。Linux 系统的核心资源有四类:CPU、内存、磁盘 IO、网络,加上一个综合指标负载(load average)。排查时先定位是哪个维度的问题,再深入分析。
1.1 一键快速定位
在排查初期,用一条命令获取全局概览:
uptime && echo "---" && top -bn1 | head -20 && echo "---" && free -h && echo "---" && vmstat 1 3
这条命令依次输出:系统负载、CPU 和进程概览、内存使用、虚拟内存统计。基本能在 10 秒内判断出卡顿方向。
1.2 分维度排查命令
|---------|----------------------|----------------------------------|
| 维度 | 命令 | 关注指标 |
| 整体负载 | uptime | load average 与 CPU 核数对比,超过核数即为过载 |
| CPU 占用 | top(按 P 排序) | %us 用户态、%sy 内核态、%wa IO 等待 |
| CPU 核明细 | mpstat -P ALL 1 | 是否单核打满、是否有软中断%soft |
| 进程 CPU | pidstat -u 1 | 哪个进程持续消耗 CPU |
| 内存 | free -h | available 是否耗尽、swap 是否在用 |
| 内存进程 | top(按 M 排序) | 哪个进程占内存最多 |
| 换页 | vmstat 1 | si/so 持续非零说明在频繁 swap |
| 磁盘 IO | iostat -xz 1 | %util 接近 100%、await 高说明磁盘瓶颈 |
| IO 进程 | iotop 或 pidstat -d 1 | 哪个进程在大量读写 |
| 网络流量 | iftop 或 nethogs | 是否被打满带宽、哪个进程占流量 |
| 网络错误 | sar -n DEV 1 | rxerr/rxdrop 是否持续增长 |
| 异常进程 | ps aux \ | awk '$8 ~ /D\ |
| 系统日志 | dmesg -T \ | tail -50 |
1.3 判断逻辑
根据 top 输出中的 CPU 状态,可以快速定位瓶颈类型:
• load 高 + %us 高:计算密集型进程,用 top 找 CPU 占用最高的进程
• load 高 + %wa 高:磁盘 IO 瓶颈,用 iostat 和 iotop 定位
• load 高 + %sy/%soft 高:内核或网络软中断开销大,检查网络包量和内核参数
• available 内存低 + swap 活跃:内存不足,检查是否有内存泄漏或进程占用过高
• %util 100% + await 高:磁盘性能不足或有坏道,检查 dmesg 中的 IO 错误
1.4 最常用的三板斧
如果时间有限,先执行这三条:
# 1. 谁在吃 CPU
top -bn1 | head -15
# 2. 谁在吃内存
ps aux --sort=-%mem | head -10
# 3. 是不是 IO 瓶颈
iostat -xz 1 3
二、打字卡顿专项排查
"打字卡"是一个比"系统卡"更具体的问题,通常不是全局资源问题,而是特定链路的延迟。需要区分场景来排查。
2.1 SSH 远程打字卡
这是最常见的场景,表现为按键后字符延迟出现,或者一串字突然一起蹦出来。
常见原因和解决方法:
网络延迟或丢包
用 ping 测试到服务器的网络质量,如果延迟高或有丢包,问题在网络层。弱网环境下可以用 mosh 替代 SSH,mosh 基于 UDP 且支持本地回显,体验远好于 SSH。
ping -c 20 <服务器IP>
SSH 服务端 DNS 反向解析
SSH 默认会对连接 IP 做反向 DNS 解析,如果 DNS 配置有问题,每次操作都会卡顿。禁用即可:
sudo sed -i 's/^#*UseDNS.*/UseDNS no/' /etc/ssh/sshd_config
sudo sed -i 's/^#*GSSAPIAuthentication.*/GSSAPIAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
MTU 不匹配
如果大包卡顿但小包正常,可能是 MTU 问题。用以下命令测试:
ping -M do -s 1472 <网关IP>
不通则说明 MTU 需要调小,或者在网关上配置 TCP MSS clamping。
2.2 本地终端打字卡
如果是在物理机或虚拟机的桌面环境中打字卡,原因通常在终端模拟器或桌面环境层面。
终端模拟器资源消耗高
gnome-terminal、konsole 等终端在某些情况下会占用大量 CPU。可以换用 alacritty 或 kitty,它们基于 GPU 加速,响应极快。
桌面合成器卡顿
KDE 的 KWin 合成器、GNOME 的动画效果可能导致输入延迟。尝试关闭合成器或禁用桌面动画。
输入法进程卡死
ibus 或 fcitx 输入法进程异常会导致输入卡顿。重启输入法:
ibus restart
# 或
fcitx5 -r
Shell 配置过重
如果每次回车都卡一下,可能是 shell 配置文件中有慢命令,比如 git 状态查询、复杂的 prompt 渲染。用裸 shell 对比测试:
bash --norc --noprofile
如果裸 shell 流畅,就需要精简 .bashrc 或 .zshrc,移除耗时插件,或者使用异步 prompt 方案如 powerlevel10k。
终端大量输出滚动
如果有进程在疯狂打印日志,终端渲染会成为瓶颈。用 Ctrl+S 暂停输出,Ctrl+Q 恢复,或者将输出重定向到文件。
2.3 编辑器中打字卡
只有在 vim 等编辑器中卡顿,通常是编辑器本身的问题:
• 插件过多或语法高亮在大文件下性能差:用 vim --noplugin 启动对比
• 文件过大:用 less 查看而非编辑,或用 vim -u NONE 裸启动
• 代码折叠导致卡顿:执行 :set nofoldenable 关闭折叠
• 交换文件写入慢:检查磁盘 IO 是否正常
2.4 快速定位法
按以下顺序测试,30 秒内可以定位问题层级:
# 1. 系统是否整体卡
uptime && top -bn1 | head -5
# 2. 排除 shell 配置问题
bash --norc --noprofile
# 3. 物理机切到 tty 测试(Ctrl+Alt+F3),排除图形环境影响
# 4. SSH 场景测试网络
ping -c 10 <服务器IP>
判断逻辑:
• tty 里也卡:系统负载或硬件问题
• tty 流畅但图形终端卡:终端模拟器、合成器或输入法问题
• 本地流畅但 SSH 卡:网络或 SSH 配置问题
• 只有某个编辑器卡:编辑器配置或大文件问题
三、内存泄漏诊断
内存泄漏是导致系统逐渐变慢、最终崩溃的常见原因。它的特点是渐进式的,容易被忽视。
3.1 什么是内存泄漏
内存泄漏的本质是:程序申请了内存但用完没有释放,导致可用内存随时间持续减少。
需要区分"内存占用高"和"内存泄漏":一个正常程序内存占用高但稳定是没问题的;一个泄漏程序哪怕每次只漏 1MB,长时间运行后也会耗尽所有内存。
3.2 为什么要关注内存泄漏
内存泄漏的危害是渐进的:
-
初期几乎无感,内存占用缓慢上升
-
中期可用内存不足,开始频繁使用 swap,系统明显变卡
-
后期 OOM Killer 随机杀掉进程,服务崩溃
-
极端情况下系统卡死,只能重启
因此在排查系统卡顿和服务不稳定时,内存使用趋势是必查项。
3.3 常见内存泄漏场景
手动内存管理语言(C/C++)
malloc/new 之后没有对应的 free/delete,或者函数提前返回、抛出异常时跳过了释放逻辑。
void bad_example() {
char *p = malloc(1024);
if (error) return; // 直接返回,p 没有释放
free(p);
}
缓存或集合只进不出
这是所有语言中最常见的泄漏类型。HashMap、List、全局缓存无限追加数据,但没有淘汰策略或清理机制。日志队列、任务队列的消费者跟不上生产者速度也属于此类。
// 典型泄漏:每个请求都 put,但从不 remove
static Map<String, Object> cache = new HashMap<>();
cache.put(requestId, data);
资源句柄未关闭
文件描述符、数据库连接、Redis 连接、Socket 没有正确关闭,这类泄漏最终会表现为 Too many open files 错误。线程创建后未退出也属于此类。
监听器和回调未注销
注册了事件监听器但对象销毁时没有反注册,定时器没有 cancel。常见于 GUI 程序、Android 开发、前端组件销毁场景。
长生命周期对象持有短生命周期引用
Java 中静态集合持有 Activity 或 Request 对象,导致整个对象图无法被 GC 回收。Python 中循环引用且定义了 del 方法,引用计数无法回收。闭包捕获了大对象且闭包被长期持有也会导致泄漏。
第三方库缺陷
驱动、JNI 库、native 扩展可能存在内存泄漏。某些版本的框架(如 Netty、Tomcat 的特定版本)也有已知的内存泄漏问题。
运维侧的类泄漏
日志文件只写不轮转导致磁盘占满、Docker 容器的 json-file 日志无限增长、/tmp 临时文件不清理。这些不是严格意义上的内存泄漏,但现象和危害类似。
3.4 排查方法
判断是否存在内存泄漏,核心是观察内存使用趋势:
# 观察特定进程的内存趋势(每 2 秒采样)
pidstat -r -p <PID> 2
# 或者用 watch 监控 RSS
watch -n 5 'ps -o pid,rss,vsz,comm -p <PID>'
# 系统整体内存趋势
free -h
sar -r 1 10
判断标准:在没有新任务涌入的情况下,进程的 RSS(实际物理内存)持续单调上升且不回落,基本可以确定存在内存泄漏。正常程序在 GC 或空闲后内存会趋于平稳或下降。
不同语言有更专业的排查工具:
• Java:jmap 导出堆转储,用 MAT 或 JVisualVM 分析
• Go:pprof 分析内存分配
• C/C++:valgrind、AddressSanitizer
• Python:tracemalloc、objgraph
四、总结
Linux 性能排查的核心思路是:先全局后局部,先资源后进程。系统卡顿用五维度模型快速定位,打字卡顿按场景分层排查,内存泄漏看趋势而非绝对值。
掌握这套方法论后,面对"机器很卡"这类模糊问题时,就可以有条理地逐步缩小范围,最终找到根因。排查工具只是手段,理解各资源之间的关联和系统的运行机制才是关键。