CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Linux 服务器入门/课时/进程与服务
RawBlame
课时目录12 节

Linux 服务器入门

  1. 01Linux 是什么
  2. 02Shell 与 Bash
  3. 03文件系统与权限
  4. 04常用命令
  5. 05编辑器与配置
  6. 06进程与服务
  7. 07用户与 sudo
  8. 08包管理
  9. 09SSH 与密钥
  10. 10防火墙与端口
  11. 11部署 CodeRoadMap
  12. 12综合练习

进程与服务

ps、systemctl、journalctl。

35 分钟

进程与服务

这一课你将能做到

  1. 用 ps aux/top 找到任意进程,说清 PID、CPU%、MEM%、STAT 的含义,并算出 Java 进程的实际内存占用
  2. 区分 kill -15 与 kill -9,写出「先优雅后强制」的停服流程;会查进程被 OOM 杀掉的证据
  3. 手写一个 systemd 单元文件让 Spring Boot JAR 开机自启 + 崩溃自动重启,并用 journalctl 查它的日志

为什么要学

「我用 nohup 起的服务,SSH 一断就没了 / 服务器一重启就找不到进程了 / OOM 了没人拉起来」——这三个问题同根同源:你把进程托管给了终端会话,而不是托管给了系统。

生产环境对进程的要求是:随系统启动(开机自启)、异常退出自动拉起(Restart=on-failure)、日志有统一归档(journald)、停止时优雅收尾(SIGTERM 让 Spring Boot 走完 shutdown 流程)。这一整套能力就是 systemd——现代 Ubuntu/CentOS 的 1 号进程。学完本课,「服务起不来」「怎么重启」「日志在哪」这类问题你都有标准答案。

先建立图像

进程是什么

程序是磁盘上的文件(backend.jar),进程是跑起来的程序实例——内核给它分配内存、CPU 时间片和一个唯一编号 PID。每个进程有父进程(PPID),构成进程树:

systemd (PID 1)                ← 所有进程的祖先,开机第一个进程
 ├─ sshd                       ← 你能 SSH 进来的原因
 │   └─ bash                   ← 你的终端 shell
 │       └─ java -jar backend.jar   ← nohup 起的服务(父进程死了就成孤儿)
 ├─ nginx (master)
 │   └─ nginx (worker × N)
 └─ mysqld

孤儿进程最终被 systemd 收养——这就是 nohup 能「断网不死」的原理,但它依然没有开机自启和崩溃重启,所以生产要用 systemd 托管。

进程管理的三条查询主线

谁在跑?        ps aux / top / systemctl status
在哪个端口?    ss -tlnp(防火墙课细讲)
日志在哪?      journalctl -u 服务名(systemd 托管的)/ 应用自己的 logs/

跟着做

步骤 1:ps——谁在跑

ps aux                       # 快照:所有用户的所有进程
ps aux | head -5             # 前 5 行(注意第 1 行就是头部说明)

# 专找 Java 进程(部署最常用的一条):
ps aux | grep java | grep -v grep
# 输出示例:www  12345  3.2 25.1 4123456 856788 ?  Sl 10:00  2:15 java -jar backend.jar
#          │    │     │   │    │        │      └ 启动时间/累计CPU时间/命令
#          │    │     │   │    └ 虚拟内存VSZ   └ S=睡眠 l=多线程 ?=无终端
#          │    │     │   └ 物理内存占用%(%MEM)
#          │    │     └ CPU%(多核可超100)
#          │    └ PID(进程号,杀进程/看日志全靠它)
#          └ 运行用户

ps -ef | grep java           # 另一种风格:全格式,能看到 PPID(父进程号)
pstree -p | head -20         # 进程树,一眼看清谁是谁的爹

步骤 2:top——动态监控与排序

top

交互键(在 top 界面里按):

P   按 CPU 排序(默认)      M   按内存排序     ← 最常用两个
1   展开每个 CPU 核          q   退出
Shift+h 显示线程            c   显示完整命令行

看懂头部三行:

%Cpu(s): 12.3 us,  2.1 sy,  0.0 ni, 84.6 id   ← us用户态 sy内核态 id空闲(id高=闲)
MiB Mem :  3936.0 total,   512.2 free,  2048.0 used,  1375.8 buff/cache
MiB Swap:  2048.0 total,  2048.0 free,    0.0 used.  1600.0 avail Mem

Java 进程内存怎么估:%MEM × 总内存 = RSS 常驻内存。top 里按 c 显示完整命令,能直接看到 -Xmx 参数。

(可选)更现代的 htop:sudo apt install htop && htop,支持鼠标和搜索 F3。

步骤 3:kill——先优雅,后强制

先造一个无害的练习进程:

sleep 600 &                    # & 放后台跑,回显里 [1] 是作业号 4567 是 PID
ps aux | grep "sleep 600" | grep -v grep

kill 4567                      # 默认发 SIGTERM(15):请你自己收拾好再走
ps aux | grep "sleep 600" | grep -v grep    # 已消失

# kill -9 的正确使用场景:进程卡死、连 SIGTERM 都不理时
sleep 600 &
PID=$(pgrep -f "sleep 600" | head -1)      # pgrep 直接拿 PID,比 grep 方便
kill -9 $PID                               # SIGKILL:内核直接回收,进程没机会善后

生产铁律:停 Java 服务先 systemctl stop(内部发 SIGTERM,Spring Boot 执行优雅关闭:停接新请求、处理完存量、释放连接),等 10-30 秒没停再 kill -9 兜底。kill -9 对数据库连接池、正在写的文件有截断风险。

步骤 4:nohup 的问题——亲手验证

# 用 nohup 起 java(没装 java 先跳过此行,用 sleep 模拟效果一样)
nohup sleep 600 > app.log 2>&1 &
ps aux | grep "sleep 600" | grep -v grep    # 在跑

# 问题1:SSH 断开重连后,你很难再「找到它、管理它」——没有服务名,只有 PID
# 问题2:服务器重启后,它不会回来
# 问题3:崩了没人拉起

三个问题 systemd 全解。下面正式上车。

步骤 5:手写 systemd 单元(本课核心)

sudo vim /etc/systemd/system/coderoadmap-backend.service

写入(注释行可留可删,路径按你的实际部署位置改):

[Unit]
Description=CodeRoadMap Spring Boot backend
After=network.target mysql.service      # 网络就绪 & MySQL 先起

[Service]
Type=simple
User=www                                # 用低权限用户跑(用户课详解)
WorkingDirectory=/www/wwwroot/coderoadmap
ExecStart=/usr/bin/java -Xms512m -Xmx1024m -jar /www/wwwroot/coderoadmap/backend.jar --spring.profiles.active=prod
Restart=on-failure                      # 异常退出自动拉起
RestartSec=5                            # 5 秒后重启
SuccessExitStatus=143                   # SIGTERM 正常退出码
LimitNOFILE=65536                       # 文件句柄上限(Java 网络服务需要)

[Install]
WantedBy=multi-user.target              # 多用户运行级别=开机自启

三条命令上车:

sudo systemctl daemon-reload                        # 改了单元文件必须先重载
sudo systemctl enable --now coderoadmap-backend     # enable=开机自启 --now=顺手立刻启动
systemctl status coderoadmap-backend --no-pager     # 绿色 active (running) = 成功

(没装 Java 时 status 会报 exit code 1——正常,包管理课装完 JDK 它就能活。)

步骤 6:systemd 日常操作五连

sudo systemctl start    coderoadmap-backend     # 起
sudo systemctl stop     coderoadmap-backend     # 停(SIGTERM 优雅关闭)
sudo systemctl restart  coderoadmap-backend     # 重启(发布新 JAR 后必做)
systemctl status        coderoadmap-backend     # 看状态(active/inactive/failed)
systemctl is-enabled    coderoadmap-backend     # 确认开机自启是否生效

崩溃自动拉起的验证(实验环境):kill -9 $(pgrep -f backend.jar),等 5 秒,systemctl status——进程换了个新 PID 又活了。这就是 Restart=on-failure 在工作。

步骤 7:journalctl——systemd 服务的日志之家

journalctl -u coderoadmap-backend -n 50 --no-pager    # 最近 50 行
journalctl -u coderoadmap-backend -f                  # 实时滚动(= systemd 版 tail -f)
journalctl -u coderoadmap-backend --since "10 min ago" --no-pager
journalctl -u coderoadmap-backend -p err --no-pager   # 只看 error 级别

注意:journal 收的是标准输出/标准错误——Spring Boot 日志同时也会写到自己的 logs/ 目录(logback 配置决定),两边都在,排障时先用 journal 确认「进程说了什么」,再下钻文件日志。

步骤 8:查「进程为什么死了」——OOM 实战排查

# 假设 java 进程莫名消失,先看 systemd 怎么说:
systemctl status coderoadmap-backend --no-pager
# 若显示 SIGKILL / Failed with result 'oom-kill',基本坐实内存不足被内核杀

# 找内核证据:
sudo dmesg -T | grep -i -E "kill|oom" | tail -5
# 典型输出:Out of memory: Killed process 12345 (java) total-vm:4G, anon-rss:2G
journalctl -k | grep -i oom | tail -5          # 内核日志的 journal 视角

看到了 OOM 证据后,方向是调小 -Xmx、加服务器内存或排查内存泄漏——不是盲目重启。

讲清楚要点

  1. nohup vs systemd 的本质区别:nohup 只解决「挂断信号」一个问题;systemd 提供开机自启、崩溃重启、优雅停止、日志归档、资源限制全套托管。生产一律 systemd(宝塔的「Java 项目」底层也是类似托管)。
  2. SIGTERM(15) 与 SIGKILL(9) 的分工:TERM 是「请求」,进程可执行清理逻辑(Spring Boot 的优雅停机);KILL 是「处决」,内核直接回收,进程毫无感知。永远先 TERM 后 KILL,中间留 10-30 秒。killall java、pkill -f 属于范围杀伤,多实例机器上慎用。
  3. Restart=on-failure 不是万能保险:它兜「异常退出」,兜不了「活着但坏掉」(线程池堵死、假死)。假死要靠健康检查 + 外部监控(如 actuator + 云监控告警)。
  4. enable 与 start 是两件独立的事:start 只管现在,enable 只管开机。enable --now 一步到位。改单元文件后必须 daemon-reload,否则 systemd 用的是旧配置。
  5. 内存三大口径别混:-Xmx 是 JVM 堆上限(不等于进程总内存);top 的 %MEM/RSS 是实际物理占用(≈堆 + 元空间 + 线程栈 + 堆外 + JIT 代码缓存);2G 小内存机器跑 -Xmx1g 的服务,加上 MySQL/Nginx 很容易 OOM——部署前先做内存预算。
  6. 前台/后台/守护三态:前台(占着终端,Ctrl+C 即死)、后台(&,终端断开风险)、守护(systemd/PM2 托管)。PM2 是 Node 生态的进程管家(platform 前端常用 pm2 start npm --name platform -- start,命令风格与 systemctl 类似:start/stop/restart/list/logs)。

你可能会懵的地方

  • jobs、&、Ctrl+Z、fg、bg 是什么关系? 这些是 shell 的「作业控制」:cmd & 直接后台跑;Ctrl+Z 把前台进程暂停挂起,bg 让它继续在后台跑,fg 调回前台,jobs 列出作业。注意后台进程仍挂在当前会话下,SSH 断开可能收到 SIGHUP 被杀——所以才有 nohup,也所以生产用 systemd。
  • ps aux 里的 STAT 字母什么意思? R=运行中,S=可中断睡眠(绝大多数进程的状态),D=不可中断睡眠(通常在等磁盘 IO,大量 D 状态=存储瓶颈),Z=僵尸(已死但父进程没收尸),T=暂停,附加符:s=会话首进程,l=多线程,<=高优先级。看到 D 堆积去查磁盘,看到 Z 堆积去查父进程。
  • 端口被占用「Address already in use」,怎么定位? ss -tlnp | grep 8080 找到占用进程的 PID → ps -p PID -o user,pid,cmd 确认是谁 → 是旧实例就 stop/restart 它,是别的服务就改端口。重启永远解决不了「谁该占这个端口」的问题。
  • systemctl status 显示 active 但接口全 503? active 只代表进程活着,不代表业务健康。要看两处:journalctl 日志有没有持续报错;curl http://127.0.0.1:8080/actuator/health 业务探活。「假活」是生产排障的常见陷阱。
  • 一台机器跑多个 Java 服务,单元文件怎么组织? 每个服务一个 .service 文件(coderoadmap-backend.service、coderoadmap-worker.service),各自独立 start/stop/restart。不要写一个脚本 fork 多个进程再交给 systemd 管——一崩全崩,日志混流。

错法对照

错法问题正解
生产用 nohup java -jar & 当正式部署无自启/无自愈/无日志归档systemd 单元托管(或宝塔 Java 项目)
一律 kill -9 停服务跳过优雅关闭,连接/事务/缓存受损先 systemctl stop(SIGTERM),必要时再 -9
覆盖 JAR 后不 restart 就验收内存里跑的还是旧代码发布流程 = 上传 + restart + 健康检查
改 .service 文件后不 daemon-reloadsystemd 仍按旧配置执行systemctl daemon-reload
服务起不来就反复 restart刷屏日志还可能触发启动限速先 journalctl -u xxx -n 50 看失败原因
-Xmx 设成机器内存的 90%堆外内存+系统进程挤爆,OOM 连环Xmx ≈ 可用内存 50-70%,留足堆外与系统
出问题先重启「看能不能好」毁掉现场,根因永远查不到先留证据:status + journalctl + dmesg

卡点急救

  1. systemctl start 失败:Unit not found:单元文件路径/文件名打错(必须在 /etc/systemd/system/ 且以 .service 结尾),改完记得 daemon-reload。
  2. 服务反复重启(start-request repeated too quickly):RestartSec 太短或进程秒崩。先看 journalctl -u xxx -n 100 找崩溃原因(多为配置错、端口被占、JDK 路径错——ExecStart 的 java 必须绝对路径),修好再启。
  3. java: command not found(单元文件里):systemd 不走你的 PATH。which java 拿绝对路径写进 ExecStart;自装 JDK 未登记到系统路径时,可在单元里加 Environment=JAVA_HOME=/opt/jdk-17 并用 $JAVA_HOME/bin/java 展开前的绝对路径。
  4. 进程消失且 status 显示 inactive(无报错):查内核 OOM:sudo dmesg -T | grep -i oom。确认后调 -Xmx / 升配 / 查泄漏;同时确认 Restart=on-failure 存在(OOM kill 视为失败会自动拉起)。
  5. journalctl 输出全在屏幕外:加 --no-pager 看一次性输出;-f 实时跟踪;--since "2026-10-06 10:00" --until "10:30" 锁定时间窗。

面试常问

  • kill 和 kill -9 的区别? kill PID 默认发 SIGTERM,进程可捕获并执行优雅关闭(Spring Boot 停接新请求、处理完存量请求、释放资源);kill -9 发 SIGKILL,不可捕获不可忽略,内核直接回收。规范是先 TERM、观察退出,卡死才 KILL。Java 常见优雅退出码 143(128+15)。

  • 如何让一个服务开机自启并在崩溃后自动重启? 写 systemd 单元:[Install] 段 WantedBy=multi-user.target 后 systemctl enable;[Service] 段 Restart=on-failure(或 always)+ RestartSec=N。改完 daemon-reload。验证:systemctl is-enabled xxx、kill -9 后看是否自动拉起。

  • ps aux 输出中你重点看哪几列? USER(谁在跑——权限排查)、PID(进程号)、%CPU/%MEM(资源水位)、VSZ/RSS(虚拟/常驻内存,Java 看 RSS 更真实)、STAT(R/S/D/Z 状态,D 多=IO 瓶颈,Z 多=僵尸)、COMMAND(启动参数,含 Xmx 配置)。

  • 线上 Java 进程突然消失,怎么排查? 固定四步:① systemctl status 服务名 看退出状态(failed/oom-kill/signal);② journalctl -u 服务名 --since "1 hour ago" 看临终日志;③ sudo dmesg -T | grep -i oom 查内核 OOM 记录;④ 应用自身日志 tail -200 logs/backend.log。结论通常是 OOM(调内存/查泄漏)或未捕获异常(修代码/加配置)。

  • systemd 和传统 init(SysV)的区别? systemd 是并行启动的服务管理器:单元化配置(.service)、依赖管理(After/Requires)、socket/定时器/日志(journald)一体化,启动快、可观测;SysV 按脚本串行启动、靠 /etc/init.d 与 chkconfig 管理。现代 Ubuntu(16.04+)/CentOS(7+)均为 systemd,systemctl 是唯一入口。

  • 僵尸进程是什么,怎么处理? 子进程已退出但父进程没调用 wait 回收,残留的进程表项。无法 kill(它已死),处理方式:让父进程回收,或杀掉/重启父进程使其被 init 收养后清理。大量僵尸说明父进程代码有问题。

自测三问

  1. nohup java -jar app.jar & 和 systemd 托管相比,少了哪些生产能力?
  2. 写出优雅重启 coderoadmap-backend 的完整命令序列(含改过单元文件的情况)。
  3. Java 进程被内核杀掉的最可能原因是什么?用什么两条命令找证据?

小结一句话

进程是跑起来的程序,服务是被系统托管的进程——用 systemd 给你的 JAR 装上「开机自启 + 崩溃自愈 + 优雅停机 + 日志归档」四件套,从「跑起来」升级到「敢上线」。

延伸

  • systemd 官方文档(freedesktop)
  • Spring Boot 官方 — systemd 安装章节
  • 下一课预告:用户与 sudo——为什么生产服务永远不该用 root 跑?deploy 用户怎么建、sudo 白名单怎么配、su 和 sudo 面试怎么答?
加载本节练习…
← 上一课编辑器与配置下一课 →用户与 sudo