相信 kill
命令大家都不陌生,程序卡住需要强制退出的场景应该是都遇到过,那么为什么有的程序直接 kill
就能退出,为什么有的需要 kill -9
才能退出呢?
想要在 Linux 中终止一个进程有两种方式,如果是前台进程(比如脚本)可以使用 Ctrl+C 快捷键键进行终止;如果是后台进程,那么需要使用 kill 命令来终止。
其实快捷键 Ctrl+C 也是发送的 kill 命令,这个后面再说。
kill 命令
命令的使用格式如下:
kill [参数] [PID(进程号)]
那么问题来了,程序如何知道一个命令想要做什么呢?答案很简单,就是发 singal(信号),命令将信号发送给程序,程序就知道了这个命令想完成的操作,比如在默认情况下,执行 kill
命令会发送的信号为 15。
信号是一个预先定义的数字,Linux 中所有的信号种类可以使用命令 kill -l
来查看。
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
代号前面的数字就是此信号的数字代码,发送数字代码的效果等同于代号。
信号 | ID | 作用和说明 |
---|---|---|
HUP | 1 | 终端断线 |
INT | 2 | 中断(同 Ctrl + C) |
QUIT | 3 | 退出(同 Ctrl + \) |
KILL | 9 | 强制终止 |
TERM | 15 | 终止 |
CONT | 18 | 继续(与STOP相反, fg/bg命令) |
STOP | 19 | 暂停(同 Ctrl + Z) |
退出信号间差异
最后单独说一下常见的几个程序退出的信号和它们之间的区别。
ID | 信号 | 说明 |
---|---|---|
2) | SIGINT | Interrupt(程序终止)信号,由 INTR 字符(通常是Ctrl-C)控制,一般用于通知前台进程组终止进程。 |
3) | SIGQUIT | Quit(程序退出)信号,与 SIGINT 信号类似,由 QUIT 字符(通常是Ctrl-\)控制。进程在收到 SIGQUIT 退出时会产生 core dump 文件,一般用来告知程序异常退出。 |
15) | SIGTERM | Terminate(程序结束)信号,与 SIGKILL 不同的是该信号可以被程序阻塞或忽略,一般用于要求程序自行安全退出,程序本身可以定义此信号的行为,因此可能延时很长 。 |
19) | SIGSTOP | Stopped(程序停止)信号,这个信号是通知进程暂停执行,并不结束,此信号不能被阻塞,处理或者忽略。 |
当使用 SIGTERM(15) 时(即:kill -15
),系统会发送 SIGTERM(15) 信号给对应的程序。当程序接收到该信号后,具体要如何处理是自己可以决定的,可以将此种方式理解为“优雅”退出。
这时候,应用程序自定义此信号的行为,比如:
- 立即停止程序
- 释放响应资源后停止程序
- 忽略该信号,继续执行程序
程序接到信号之后,退出前一般会进行一些“准备工作”,如资源释放、临时文件清理等等,如果准备工作做完了,再进行程序的终止。
但是,如果在"准备工作"进行过程中,遇到阻塞或者其他问题导致无法成功,那么应用程序可以选择忽略该终止信号。
而当使用 SIGKILL(9) 时,系统会强硬结束程序,会在系统层面直接要求程序立即结束,不能被阻塞和忽略。
这也就是为什么有的时候单纯使用 kill
命令是没办法"杀死"应用的原因。并且在执行 kill -9
时,应用程序是没有时间进行"准备工作"的,所以这通常会带来一些副作用,比如数据丢失、状态丢失等等。
额外补充一下,比如在容器中,因为程序的不同,所以需要使用 Dockerfile 的 STOPSIGNAL
参数来定义容器镜像的退出方式,防止出现程序卡死无法正常退出容器的问题。
Java 中的退出信号
在 Linux 中,Java 应用是作为一个独立进程运行的,Java 程序的终止运行是基于 JVM 的关闭实现的,JVM 关闭方式分为3种:
- 正常关闭:当最后一个非守护线程结束或者调用了 System.exit 或者通过其他特定平台的方法关闭(接收到 SIGINT(2)、SIGTERM(15)信号等)
- 强制关闭:通过调用 Runtime.halt 方法或者是在操作系统中强制 kill(接收到 SIGKILL(9)信号)
- 异常关闭:运行中遇到 RuntimeException 异常等。
JVM 进程在接收到 SIGTERM 信号通知的时候,是可以做一些清理动作的,比如删除临时文件等。当然,开发者也是可以自定义做一些额外的事情的,比如让容器停止,让 dubbo 服务下线等。而这种自定义退出动作的方式,是通过 JDK 中提供的 shutdown hook
实现的。
JDK 提供了 Java.Runtime.addShutdownHook(Thread hook)
方法,可以注册一个 JVM 关闭的钩子。例子如下:
package com.hollis;
public class ShutdownHookTest {
public static void main(String[] args) {
boolean flag = true;
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("hook execute...");
}));
while (flag) {
// app is runing
}
System.out.println("main thread execute end...");
}
}
然后执行命令 kill -15
:
➜ jps
6520 ShutdownHookTest
6521 Jps
➜ kill 6520
控制台输出内容:
hook execute...
Process finished with exit code 143 (interrupted by signal 15: SIGTERM)
可以看到,当使用默认 kill 信号关闭进程的时候,程序会先执行用户定义的 shutdownHook 方法后再退出。
如果执行命令 kill -9
:
➜ kill -9 6520
控制台输出内容:
Process finished with exit code 137 (interrupted by signal 9: SIGKILL)
可以看到,当使用 kill -9 强制关闭进程的时候,程序并没有执行 shutdownHook,而是直接退出。
附录
参考链接
- Termination Signals (The GNU C Library) - GNU
- SIGINT And Other Termination Signals in Linux - Baeldung
- SIGINT、SIGQUIT、 SIGTERM、SIGSTOP区别 - CSDN
本文由 柒 创作,采用 知识共享署名4.0
国际许可协议进行许可。
转载本站文章前请注明出处,文章作者保留所有权限。
最后编辑时间: 2022-06-16 15:26 PM