kill -9 24831
命令一敲,Java 进程瞬间没了。
干净是真干净,连 JVM 跟你说遗言的机会都没有。线上要是谁把kill -9当正常停机命令用,我看到一般也会拦一下,这玩意更适合“进程已经没救了”的时候,不适合拿来日常发布。
有次我看到一段停机脚本,写得相当豪爽:
pid=$(cat /data/run/order-api.pid)
kill -9 "$pid"
rm -f /data/run/order-api.pid
我第一眼就不太信这个脚本。
不是担心它杀不掉,而是担心它杀得太彻底。
Java 服务停机时,后面可能还挂着线程池任务、数据库事务、MQ 消费、日志缓冲区,甚至还有正在处理一半的 HTTP 请求。你直接一个-9,操作系统确实省事了,业务可不一定省事。
正常情况下,我更愿意这样停:
kill 24831
别看就少了个-9,差别挺大。
Linux 下普通kill默认发送的是SIGTERM,也就是 15 号信号。它表达的意思更像:
“准备关门了,把手上的活收一下。”
而:
kill -9 24831
发送的是SIGKILL。
这个信号不会交给 JVM 处理,操作系统直接把进程干掉。Java 代码捕获不了,也不能忽略。
这地方最容易坑人的就是 JVM Shutdown Hook。
比如服务里有个内存队列,停机前我希望把还没处理的数据记录下来:
public final class PendingTaskRecorder {
private final BlockingQueue<Long> pendingIds;
public PendingTaskRecorder(BlockingQueue<Long> pendingIds) {
this.pendingIds = pendingIds;
}
public void registerShutdownTask() {
Runtime.getRuntime().addShutdownHook(
new Thread(() -> {
System.out.println("service stopping, pending=" + pendingIds.size());
while (!pendingIds.isEmpty()) {
Long taskId = pendingIds.poll();
if (taskId != null) {
System.out.println("unfinished task: " + taskId);
}
}
}, "shutdown-recorder")
);
}
}
正常kill PID,JVM 收到SIGTERM,Shutdown Hook 有机会执行。
kill -9?
这段代码跟没写差不多。
Spring 项目也一样。
很多服务停机时,会在 Bean 销毁阶段关闭资源:
@Component
public class ReportWorker {
private final ExecutorService pool =
Executors.newFixedThreadPool(4);
@PreDestroy
public void close() {
pool.shutdown();
try {
if (!pool.awaitTermination(8, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
pool.shutdownNow();
}
}
}
这种代码我在线上项目里是愿意留的。
因为正常停机时,Spring 容器关闭,@PreDestroy会有执行机会,线程池至少能把已经接进来的任务处理一下。
但你要是直接kill -9,Spring 容器的生命周期也别谈了。
线程池没收尾,连接没主动释放,业务清理逻辑没执行。
数据库连接最终会因为 TCP 断开被清掉,但“连接最后会被清掉”和“业务正确完成”完全是两回事。
比如代码正跑到这里:
@Transactional
public void settle(Long orderId) {
orderRepository.markPaid(orderId);
ledgerService.writeLedger(orderId);
eventPublisher.publish(orderId);
}
如果都在一个数据库事务里,进程突然死亡,数据库通常还能依靠事务机制回滚。
我反而不太怕这种。
我更怕这种:
public void finishOrder(Long orderId) {
orderRepository.markFinished(orderId);
remoteCouponClient.release(orderId);
messageSender.sendFinished(orderId);
}
数据库改完了,远程接口刚调出去,消息还没发送,进程被kill -9了。
重启以后到底应该从哪一步继续?
这就不是 Linux 命令的问题了,而是业务一致性问题。
还有 MQ 消费。
消费者刚把消息取下来,业务执行到一半,进程直接消失。如果 ACK 时机、事务边界、幂等没设计好,重启以后很容易遇到重复消费,严重一点就是业务状态对不上。
所以我平时停 Java 服务,顺序比较固定。
先发SIGTERM:
kill "$pid"
然后看进程有没有正常退出:
for i in {1..20}; do
if ! kill -0 "$pid" 2>/dev/null; then
echo "process stopped"
exit 0
fi
sleep 1
done
20 秒还不退,我才会继续查。
先jstack看线程卡在哪:
jstack "$pid" > /tmp/java-stop-$pid.log
如果看到大量线程堵在数据库连接、HTTP 调用或者某把锁上,这时候处理的是“为什么停不下来”,不是上来就给它一发-9。
真到了服务僵死、Full GC 卡住、Shutdown Hook 本身挂死,或者进程已经失去响应,那kill -9当然可以用。
我从来不反对这个命令。
我反对的是把它写成:
stop() {
kill -9 "$1"
}
然后堂而皇之叫“优雅停机”。
这跟关电脑不点关机,天天直接拔插头没多大区别。
Java 服务能不能安全退出,看的不是进程最后有没有消失,而是它消失之前,该停的流量停了没有,该处理完的任务处理了没有,该提交或者回滚的数据有没有落稳。
kill -9应该是最后那把锤子。
天天拿锤子关服务,领导看了想开除人,我能理解。