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 查看历史性能数据,建立性能基线便于对比
  • 使用 nohuptmux 确保关键任务在终端关闭后继续运行
  • 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.confvm.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:/binSHELL=/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 管理进程生命周期。

延伸阅读

↑ 回到顶部