FAQ-4:进程与性能常见问题
预计阅读时间:13 分钟
📖 目录
FAQ-4:进程与性能常见问题(Q44-Q57)
问题速查表
| 问题 | 章节 |
|---|---|
| Q44: 负载高但 CPU 使用率低? | Q44 |
| Q45: 如何找出 CPU/内存占用最高的进程? | Q45 |
| Q46: 僵尸进程如何处理? | Q46 |
| Q47: 如何查看进程打开的文件/环境变量/线程? | Q47 |
| Q48: 为什么 free 显示内存少但实际可用很多? | Q48 |
| Q49: 如何限制进程 CPU 使用? | Q49 |
| Q50: 如何查看历史性能数据? | Q50 |
| Q51: 如何让进程在终端关闭后继续运行? | Q51 |
| Q52: 如何查看启动耗时排名? | Q52 |
| Q53: 进程无法 kill(即使 kill -9)? | Q53 |
| Q54: 如何实时监控磁盘 I/O? | Q54 |
| Q55: 如何查看系统运行时间? | Q55 |
| Q56: 如何批量终止进程? | Q56 |
| Q57: 如何查看进程启动时间? | Q57 |
Q44: 负载高但 CPU 使用率低?
Load Average(负载均值)不仅包含 CPU 使用,还包含 I/O 等待和不可中断睡眠(D 状态)的进程。高负载低 CPU 通常意味着 I/O 瓶颈。
# 查看负载和 CPU 使用率
uptime
# 输出:14:30:01 up 10 days, load average: 8.50, 6.20, 3.10
# 3 个数字分别表示 1/5/15 分钟的平均负载
# 查看 I/O 等待
iostat -x 1 3 # 每秒采样,共 3 次
# 关注 %util(磁盘利用率)和 await(平均等待时间)
# 找出 D 状态(不可中断睡眠,通常是 I/O 阻塞)的进程
ps aux | awk '$8 ~ /D/ {print}'
# D 状态进程无法被 kill -9 终止,只能等 I/O 完成或重启
Q45: 如何找出 CPU/内存占用最高的进程?
# 按 CPU 使用率排序(-%cpu 降序)
ps aux --sort=-%cpu | head -10
# 按内存使用率排序(-%mem 降序)
ps aux --sort=-%mem | head -10
# top 交互式工具:按 P 按 CPU 排序,按 M 按内存排序
# top -o %CPU:启动时按 CPU 排序
Q46: 僵尸进程如何处理?
僵尸进程(Zombie,状态 Z)是已终止但父进程未回收其退出状态的进程。不占 CPU/内存,但占进程表条目。
# 查找僵尸进程
ps aux | awk '$8 ~ /Z/ {print}'
# 输出示例:root 1234 0.0 0.0 0 0 ? Z 10:00 0:00 [defunct]
# 找到父进程 PID
ps -o ppid= -p 1234 # 输出父进程 PID
# 终止父进程(让 init/systemd 回收僵尸进程)
kill <父进程PID>
# 如果父进程也是僵尸,只能重启
Q47: 如何查看进程打开的文件/环境变量/线程?
# 查看进程打开的文件描述符
lsof -p PID
# 查看进程的环境变量
cat /proc/PID/environ | tr '\0' '\n' # 用换行替换 null 字符分隔
# 查看进程的线程
ps -Lf -p PID # LWP(Light Weight Process)列是线程 ID
top -H -p PID # 在 top 中显示线程
Q48: 为什么 free 显示内存少但实际可用很多?
Linux 内核会将空闲内存用作 buffer/cache(磁盘缓存),以加速 I/O。这部分缓存在应用需要时可以立即释放。
# free -h:人类可读格式
free -h
# total used free shared buff/cache available
# Mem: 16Gi 4.2Gi 2.1Gi 256Mi 9.7Gi 11Gi
# 关键指标:
# free:完全未使用的内存(可能很小,这是正常的)
# buff/cache:内核缓存(需要时可回收)
# available:实际可用内存 = free + 可回收的 cache
# 判断内存是否够用看 available 列,而非 free 列
Q49: 如何限制进程 CPU 使用?
# cpulimit:按百分比限制进程 CPU 使用率
# -p:进程 PID -l:限制百分比
cpulimit -p PID -l 50 # 限制到 50%
# systemd 服务限制
# CPUQuota=50% # 限制该服务最多使用 50% CPU
# Docker 容器限制
docker run --cpus="0.5" image # 限制 0.5 个 CPU 核心
Q50: 如何查看历史性能数据?
# sar(System Activity Reporter):查看历史性能数据
# 需要安装 sysstat 包:sudo apt install sysstat
# 并启用采集:编辑 /etc/default/sysstat,设 ENABLED="true"
# 查看今天 CPU 使用历史
sar -u
# 查看指定日期
sar -u -f /var/log/sysstat/sa15 # 查看 15 号的数据
# 查看内存历史
sar -r
Q51: 如何让进程在终端关闭后继续运行?
# nohup:忽略 SIGHUP 信号(终端关闭时发出)
# &:后台运行
# > /dev/null 2>&1:丢弃所有输出
nohup command > /dev/null 2>&1 &
# disown:从当前 shell 的作业列表中移除
# 即使不用 nohup,disown 也能防止进程被 SIGHUP 终止
command &
disown
# screen/tmux:创建持久化会话
screen -S mysession # 创建名为 mysession 的会话
# 断开:Ctrl+A D
# 重连:screen -r mysession
tmux new -s mysession # tmux 方式
# 断开:Ctrl+B D
# 重连:tmux attach -t mysession
Q52: 如何查看启动耗时排名?
# systemd-analyze:分析系统启动时间
systemd-analyze blame | head -20
# 按耗时从大到小排列各服务启动时间
# 输出示例:
# 32.123s NetworkManager-wait-online.service
# 5.432s docker.service
# 2.100s sshd.service
Q53: 进程无法 kill(即使 kill -9)?
D 状态(Uninterruptible Sleep)的进程无法被任何信号终止,包括 kill -9。这通常是因为进程正在等待 I/O 操作完成(如磁盘、NFS)。
# 查找 D 状态进程
ps aux | awk '$8 ~ /D/'
# D 状态进程的特征:
# - ps 中状态列为 D
# - top 中显示为 D
# - kill -9 无效
# 解决方法:
# 1. 等待 I/O 完成(可能需要几分钟)
# 2. 检查是否有磁盘故障或 NFS 挂载问题
# 3. 如果是 NFS 挂载,umount -f -l 强制卸载
# 4. 最后手段:重启系统
Q54: 如何实时监控磁盘 I/O?
# iostat -x:扩展磁盘统计
# 每秒刷新,持续显示
iostat -x 1
# 关注:%util(利用率)、await(平均等待时间)、r/s w/s(读写次数)
# iotop:类似 top 的磁盘 I/O 监控
# 需要安装:sudo apt install iotop
sudo iotop
Q55: 如何查看系统运行时间?
# uptime:最简单的方式
uptime
# 输出:14:30:01 up 10 days, 3:22, 2 users, load average: 1.20, 0.80, 0.50
# uptime -p:人类可读格式
uptime -p
# 输出:up 10 days, 3 hours, 22 minutes
# cat /proc/uptime:精确到秒
cat /proc/uptime
# 输出:874922.34 1749844.68
# 第一个数字是运行秒数,第二个是所有 CPU 的空闲时间总和
Q56: 如何批量终止进程?
# pkill:按模式匹配进程名
# -f:匹配完整命令行(不仅是进程名)
pkill -f "python3 server.py"
# killall:按进程名精确匹配
killall nginx
# xargs 方式:先找到 PID 再批量 kill
ps aux | grep "node" | grep -v grep | awk '{print $2}' | xargs kill
Q57: 如何查看进程启动时间?
# ps -eo:自定义输出格式
# lstart:启动时间 cmd:命令
ps -eo pid,lstart,cmd | grep nginx
# 输出示例:
# 1234 Mon Jun 15 09:30:00 2026 nginx: worker process
真实案例
案例 A:负载飙高但 CPU 使用率低——NFS 挂载超时导致 D 状态进程堆积
现象:服务器 load average 飙到 30+,但 top 显示 CPU 使用率只有 5%。Web 应用响应极慢。
$ uptime
14:30:01 up 10 days, load average: 32.10, 28.50, 15.20
$ top -bn1 | head -5
%Cpu(s): 2.3 us, 1.1 sy, 0.0 ni, 95.8 id, 0.8 wa
# 找 D 状态进程
$ ps aux | awk '$8 ~ /D/' | wc -l
47
根因:NFS 服务器网络中断,47 个进程全部卡在 D 状态等待 NFS I/O。load average 包含 D 状态进程,但它们不消耗 CPU。
修复:umount -f -l /mnt/nfs 强制卸载 NFS 挂载点,D 状态进程立即解除阻塞。后续在 fstab 中添加 soft,timeo=10 选项,避免 NFS 超时无限等待。
案例 B:僵尸进程堆积导致进程表耗尽
现象:系统无法创建新进程,报 bash: fork: Cannot allocate memory,但 free 显示内存充足。
# 查看僵尸进程数量
$ ps aux | awk '$8 ~ /Z/' | wc -l
1823
# 查看系统最大进程数
$ cat /proc/sys/kernel/pid_max
32768
根因:父进程(一个自定义守护进程)在子进程退出后没有调用 wait() 回收,1823 个僵尸进程占满了进程表。这是 C/C++ 编程中常见的资源泄漏问题,Shell 脚本中一般由 systemd 自动处理。
修复:修复父进程代码,在 SIGCHLD 信号处理函数中调用 waitpid(-1, NULL, WNOHANG) 回收子进程。临时方案:重启父进程。
案例 C:nohup 进程在终端关闭后仍被终止
现象:用 nohup python3 server.py & 启动的 Web 服务,SSH 断开后几秒就停了。
# 检查进程是否还在
$ ps aux | grep server.py
# 无输出——进程已终止
# 原因:nohup 只防 SIGHUP,但 systemd user session 会发送 SIGTERM
# 检查 systemd 是否管理了该用户会话
$ systemctl --user status
# User session 管理器会在所有终端关闭后终止用户进程
根因:某些发行版的 systemd 配置了 KillUserProcesses=yes,用户登出后会终止所有用户进程。
修复:将服务配置为 systemd service(Type=simple),或用 loginctl enable-linger 允许用户进程在登出后继续运行。
预防措施
- 设置 load average 监控告警(阈值建议为 CPU 核心数的 2 倍),参考 3.12:系统监控与告警 监控
- D 状态进程无法被 kill -9 终止,排查磁盘/NFS 等 I/O 源是关键
- 关键服务使用 systemd 管理,systemd 会自动回收子进程并处理重启策略
- 编写守护进程时必须处理 SIGCHLD 信号并调用 wait(),避免僵尸进程堆积
- NFS 挂载使用
soft,timeo=10选项,避免 I/O 超时导致 D 状态进程 - 定期用
ps aux | awk '$8 ~ /Z/' | wc -l检查僵尸进程数量 - 内存不足时优先排查
ps aux --sort=-%mem | head -10,设置 OOM 分数调整关键进程保护 - 定期用
sar -u -r -n DEV查看历史性能数据,建立性能基线便于对比 - 使用
nohup或tmux确保关键任务在终端关闭后继续运行 - OOM 分数调整(
oom_score_adj)仅临时生效,重启后需重新设置 - 使用
systemd-analyze plot > boot.svg可视化启动流程,排查启动耗时
案例 D:OOM Killer 误杀关键进程
现象:MySQL 数据库突然被系统终止,dmesg 显示 Out of memory: Killed process。
# 查看 OOM 日志
$ dmesg | grep -i "oom\|killed"
[12345.678] Out of memory: Killed process 5678 (mysqld) total-vm:8192000kB, anon-rss:4096000kB
# 查看 OOM 分数(越高越容易被杀)
$ cat /proc/5678/oom_score
850
# 保护关键进程:降低 OOM 分数(-1000 表示永不杀)
$ echo -1000 > /proc/5678/oom_score_adj
# 持久化配置(重启后生效)
$ echo "vm.overcommit_memory=2" | sudo tee -a /etc/sysctl.conf
$ echo "vm.overcommit_ratio=80" | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p
根因:系统内存不足,内核 OOM Killer 选择 oom_score 最高的进程终止。MySQL 因内存使用量大而被选中。OOM Killer 的选择策略基于进程的内存使用量、运行时间和 oom_score_adj 调整值。
修复:短期调整 oom_score_adj 保护关键进程;长期增加内存或优化应用内存使用。配置 /etc/sysctl.conf:vm.overcommit_memory=2(严格模式,不允许 overcommit),vm.overcommit_ratio=80(可用内存的 80% 允许分配)。用 dmesg | grep -i oom 定期检查 OOM 事件。
案例 E:D 状态进程导致系统卡死无法 SSH
现象:服务器完全无响应,SSH 连接超时,但 ping 仍通。
# 通过控制台或 IPMI 登录后查看
$ ps aux | awk '$8 ~ /D/' | wc -l
234
# 检查 D 状态进程的等待通道
$ cat /proc/1234/wchan
nfs_wait_request ← 卡在 NFS I/O
# 检查 NFS 挂载状态
$ mount | grep nfs
192.168.1.20:/data on /mnt/nfs type nfs4 (rw,...)
# 查看 D 状态进程的系统调用
$ cat /proc/1234/syscall
18 0xffffffffffffff8c 0x7fff1234 0x100 0 0 0 0 0 0
# 紧急修复:强制卸载 NFS
$ sudo umount -f -l /mnt/nfs
# D 状态进程立即解除阻塞
根因:NFS 服务器宕机,所有访问 NFS 的进程卡在 D 状态(不可中断睡眠),系统负载飙升,SSH 等交互式服务无法获得 CPU 时间片。D 状态进程无法被 kill -9 终止。
修复:通过控制台执行 umount -f -l /mnt/nfs 强制卸载 NFS,D 状态进程立即解除阻塞。后续在 fstab 中添加 soft,timeo=10,retrans=3 选项,避免 NFS 超时无限等待。监控 D 状态进程数量:ps aux | awk '$8 ~ /D/' | wc -l。
案例 F:Cron 任务未按预期执行
现象:crontab 中配置的任务没有按时执行,但手动执行脚本正常。
# 查看 cron 日志
$ grep CRON /var/log/syslog
Jul 31 02:30:01 server CRON[12345]: (root) CMD (/usr/local/bin/backup.sh)
# 日志显示 cron 确实触发了命令
# 检查脚本环境变量
$ crontab -l
30 2 * * * /usr/local/bin/backup.sh
# 问题:cron 环境变量 PATH 极简,不含 /usr/local/bin
# 解决:在 crontab 中设置完整 PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 2 * * * /usr/local/bin/backup.sh
# 调试 cron 任务
# 在 crontab 中添加日志输出
30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# 检查脚本权限
$ ls -la /usr/local/bin/backup.sh
-rwxr-xr-x 1 root root 1234 Jun 15 10:00 /usr/local/bin/backup.sh
# 确保有执行权限
根因:cron 的默认环境变量极少(仅 PATH=/usr/bin:/bin,SHELL=/bin/sh),脚本中使用的命令(如 mysqldump)可能不在 cron 的 PATH 中。另外,cron 执行时的工作目录是调用者的 HOME 目录,不是脚本所在目录。
修复:在 crontab 顶部设置 PATH=...,或在脚本中使用绝对路径。用 env -i HOME=$HOME SHELL=/bin/bash PATH=/usr/bin:/bin crontab -l 测试 cron 环境。在脚本开头添加 set -euo pipefail 和日志输出,便于排查。确认脚本有执行权限 chmod +x script.sh。
案例 G:systemd slice 内存限制导致 Java 应用 OOM
现象:Java 应用部署为 systemd 服务后频繁被 OOM Killer 终止,但 free -h 显示系统内存充足。
# 检查 systemd 对服务的内存限制
$ systemctl show myapp.service -p MemoryMax
MemoryMax=536870912 ← 限制为 512MB
# 检查 Java 堆大小配置
$ cat /opt/app/start.sh
java -Xmx4g -jar app.jar ← 申请 4GB 堆内存
# 问题:Java 堆 4GB 远超 systemd 内存限制 512MB
# systemd 在内存使用超过 MemoryMax 时会直接杀掉进程
# 查看 OOM 日志
$ journalctl -u myapp | grep -i "oom\|killed"
Jul 31 10:00:01 server systemd[1]: myapp.service: Main process exited, code=killed, status=9/KILL
# 修复:调整 systemd 内存限制
$ sudo systemctl edit myapp.service
[Service]
MemoryMax=6G
MemoryHigh=5G
# 重新加载并重启
$ sudo systemctl daemon-reload
$ sudo systemctl restart myapp
根因:systemd 的 MemoryMax 是硬限制,超过即触发 SIGKILL;MemoryHigh 是软限制,超过后 systemd 会尝试回收内存。Java 堆大小配置与 systemd 内存限制不匹配是常见问题。
修复:调整 MemoryMax 为 Java 堆大小 + 1-2GB(非堆内存开销)。用 systemctl show service -p MemoryMax 检查限制。建议同时设置 MemoryHigh 作为预警线。
案例 H:僵尸进程被 systemd 自动回收后子进程孤儿化
现象:父进程意外退出后,其子进程变成孤儿进程,被 init/systemd 收养,但行为异常。
# 查看孤儿进程
$ ps aux | grep -E "PPID.*1" | head -5
root 5678 0.0 0.1 ... /opt/app/worker.sh
root 5679 0.0 0.0 ... /opt/app/worker.sh
# 检查原父进程是否已退出
$ ps -p 5678 -o pid,ppid,cmd
PID PPID CMD
5678 1 /opt/app/worker.sh ← PPID=1 表示已被 init 收养
# 孤儿进程的问题:失去父进程的监控和资源管理
# 如果原父进程有资源限制(cgroup),孤儿进程会逃逸到根 cgroup
# 修复:用 systemd 管理子进程
# 创建独立的 service 管理 worker,而不是用脚本手动 fork
$ cat /etc/systemd/system/worker.service
[Service]
Type=simple
ExecStart=/opt/app/worker.sh
Restart=always
RestartSec=3
CPUQuota=50%
MemoryMax=1G
根因:Shell 脚本或应用程序 fork 子进程后父进程退出,子进程变成孤儿进程(PPID=1)。孤儿进程失去了父进程的资源限制和监控,可能导致资源泄漏。正确做法是用 systemd 管理服务进程。
修复:将长运行进程配置为 systemd 服务,利用 systemd 的 cgroup 资源限制和自动重启机制。避免手动 fork daemon,让 systemd 管理进程生命周期。
延伸阅读
- 2.4:进程管理 进程管理——ps/top/htop、信号与后台任务管理
- 4.6:Linux 性能调优 Linux 性能调优——USE 方法剖析 CPU、内存、磁盘 I/O 与网络
- 3.12:系统监控与告警 系统监控与告警——Prometheus + Grafana 监控方案与告警
- 3.15:Cron 定时任务 Cron 高级定时任务——cron 语法、环境变量与常见陷阱
- 2.7:日志与故障排查 日志与故障排查——dmesg、journalctl 与系统日志分析
- 1.9:重定向与管道 重定向与管道——进程输出重定向与管道操作