说起杀死进程第一想到的绝对是kill这个命令,看一下man page的介绍

The kill utility sends a signal to the processes specified by the pid operand(s).

kill是一个向进程发送信号的命令,为什么进程需要信号呢?假设一个需要运行很久的程序正在运行,这时候你想把它停掉,尴尬,只能拔电源了。据此,linux操作系统提供了一系列的信号,这些信号可在程序运行时发送给进程,可以使用

1
kill -l

来查看有哪些信号,不过常用的不多,man page里面介绍的常用的信号有如下几种

  • 1 HUP (hang up)
  • 2 INT (interrupt)
  • 3 QUIT (quit)
  • 6 ABRT (abort)
  • 9 KILL (non-catchable,non-ignorable kill)
  • 14 ALRM (alarm clock)
  • 15 TERM (software termination signal)

其中

1
kill $pid

默认情况下是TERM(15)信号。最常用的是INT(2)和TERM(15),INT是强制终止,不敢进程在干什么,都必须终止,而且该信号不能被程序捕获重写,TERM是终止,但是可以被我们的程序捕获重写,这样我们就可以利用它来清理现场了,几乎所有的编程语言都可以捕获kill信号。

我们来写一段捕获信号处理的代码来做个简单的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import sun.misc.Signal;
import sun.misc.SignalHandler;
public class KillTest implements SignalHandler {
private int i = 0;
public static void main(String[] args) {
SignalTest signalTest = new SignalTest();
Signal.handle(new Signal("HUP"), signalTest);
Signal.handle(new Signal("INT"), signalTest);
//Signal.handle(new Signal("QUIT"), signalTest);// 该信号不能捕获
Signal.handle(new Signal("ABRT"), signalTest);
//Signal.handle(new Signal("KILL"), signalTest);// 该信号不能捕获
Signal.handle(new Signal("ALRM"), signalTest);
Signal.handle(new Signal("TERM"), signalTest);
while (true) {
killTest.incrI();
System.out.print(killTest.getI());
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getI() {
return this.i;
}
public void setI(int i) {
this.i = i;
}
public int incrI() {
this.i++;
return this.i;
}
@Override public void handle(Signal signal) {
System.out.printf("receive signal " + signal.getName() + "-" + signal.getNumber());
}
}

在常用的信号中QUIT和KILL是不能捕获的,也就是如果别人执行了这个kill,那么你的进程立马就得死,没得商量,这也可以理解,如果有人恶意的把所有信号都捕获且忽略了,那想杀掉这个进程那不是只能拔电源了?

那么这种捕获通常有些什么用处呢?

最常用的像发布代码,发布java代码一般是先把新包拉到服务器上,备份旧包,停止服务(如摘除节点),杀掉进程,新包替换旧包,拉起新进程,提供服务。

如果原来的进程有一个文件锁,这时杀掉了原进程(假设被杀死时不做任何处理,或者使用kill -9),文件锁还在,那新的进程拉起来后一直处于异常状态,因为原来的锁一直在,没人可以解掉。

刚刚如果你捕获了kill -15 信号并清理了锁文件,恰好发布系统也是使用kill -15,那么程序很完美。

假设不幸用了kill -9 那也无能为力了,但是这毕竟是我们自己的程序,你应该知道怎么杀死它,所以,修改一下发布脚本吧,别那么粗暴。

当然在java中可以不用捕获信号来处理,我们可以利用更简单方便的ShutdownHook

把上面代码修改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class KillTest {
private int i = 0;
public static void main(String[] args) {
KillTest killTest = new KillTest();
Runtime.getRuntime().addShutdownHook(
new ShutDownThread(killTest)
);
while (true) {
killTest.incrI();
System.out.print(killTest.getI());
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getI() {
return this.i;
}
public void setI(int i) {
this.i = i;
}
public int incrI() {
this.i++;
return this.i;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ShutDownThread extends Thread {
public KillTest killTest;
public ShutDownThread(KillTest killTest) {
this.killTest = killTest;
}
public int getKillTestI() {
return killTest.getI();
}
@Override public void run() {
super.run();
int i = getKillTestI();
System.out.print("\nI'm shutdown hook..."+i);
}
}

通过这种方法就不用注册那么多信号了,毕竟能捕获的那么的,我猜测这个的实现也是基于信号的封装,但是这里有一个注意的地方是,这个hook在程序正常退出,异常退出(如内存不够等)也会执行,正常退出在上面的例子中得做一个简单的区分。

最后总结一下,如果程序退出需要处理一些东西,捕获一下信号或者加个hook,说不定谁会去服务器上执行一下kill呢,就算没有,也要防止类似发布系统等对你程序的影响。执行kill最好也不要kill -9,除非真的杀不死~