原标题:Linux系统下如何优雅地关闭Java进程?

前言

Linux系统下如何kill掉一个后台Java进程,相信童鞋们都知道如何操作。首先使用ps命令查找该Java进程的进程ID,然后使用kill命令进行杀掉。命令如下:

(1)ps查进程ID

[user@data2 ~]$ ps -ef | grep Test
user 2095020809 0 21:30 pts/1 00:00:00 java -jar Test.jar
user 21030 20996 0 21:30 pts/2 00:00:00 grep Test

(2)kill杀进程

[user@data2 ~]$ kill -9 20950

再使用ps命令查该进程,发现进程Test.jar已经被杀掉。使用“kill -9 $pid”杀Java进程,干净利落。但该方法是不是结束Java后台进程的较好方法呢?

场景

思考下面的场景:

“开发一个Java后台程序,其功能是不停地扫描Linux系统下的某个ftp目录。如果有文件,就经过数据转换写入到数据库中;如果没有文件,就sleep一秒钟。ftp目录下的文件不断地上传,Java程序处理完一个文件,就将该文件移到备份目录下面。”

该场景涉及Java程序进行文件打开、文件读取、文件备份、数据库连接、数据库写入等操作。因为文件句柄和数据库连接在Linux系统中是有限的资源,所以文件和数据库操作完成,需要进行关闭。

如果用户直接使用“kill -9”杀掉一个后台正在读取文件并写入数据库的Java进程。那么有可能文件和数据库连接没有正确关闭,而且数据文件也没有标识是否处理完成,或处理到哪个位置。

应用

近日在处理分布式消息Kafka的消息读取的工作,同样面临着上述场景的问题。如果读取了一条消息,在处理该消息之前进程被用户强行杀掉。那么该条消息就丢失了,既不在消息队列,也不在数据库或本地文件中。所以需要让后台Java进程在被杀的时候得到通知,这样就能安排好进程的“后事”。

Java中要得到kill信号通知,需要继承自“SignalHandler”类。完整实现代码如下:

importsun.misc.Signal;
importsun.misc.SignalHandler;
publicclassEngineimplementsSignalHandler{
privatebooleanbeKilling=false;
publicstaticvoidmain(String[]args){
Engine signalHandler=newEngine;
Signal.handle(newSignal("TERM"),signalHandler);// kill -15
Signal.handle(newSignal("INT"),signalHandler);// Ctrl+c
Signal.handle(newSignal("USR2"),signalHandler);// kill -12
String zookeeper="host1:2181,host2:2181,host3:2181";
String groupId="gp01";
String topic="test";
KafkaConsumer kc=newKafkaConsumer(zookeeper,groupId,topic);
//对读到的每一条kafka消息做处理
while(!signalHandler.beKilling&&kc.hasNext){
byte[]data=kc.getNext;
//转为String类型
String res=newString(data);
//消息处理逻辑
ProcessData d=newProcessData;
d.processingMessage(res);
}
System.out.println("bye!");
}
@Override
publicvoidhandle(Signal arg0){
// TODO Auto-generated method stub
beKilling=true;
}
}

说明:

(1)需要引用sun.misc的两个类:Signal和SignalHandler;

(2)添加一个变量bKilling用于标识该进程是否正在被杀,如果是,则跳出循环不在接收Kafka消息;

(3)一般接收“kill -15”的信号。

在eclipse开发环境中,引用sun.misc会报找不到类问题,其实该包在rt.jar包中。需要配置一下IDE,忽略掉Signal相关的错误和警告,如下图所示:


如果尝试响应其他kill信号,运行程序时会报如下错误:

[user@data2 test]$ java -jar Test.jar
Exception in thread "main" java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGKILL
at sun.misc.Signal.handle(Signal.java:166)
at TestSignal.main(TestSignal.java:12)

进程的启动与关闭

Java后台进程启动和关闭

启动:

[user@data2 test]$nohup java -jar Test.jar &

查看日志:

[user@data2 test]$ more nohup.out
running ......
running ......
…

查看进程:

[user@data2 test]$ ps -ef | grep Test.jar
unicom 2800527711 0 22:24 pts/0 00:00:00 java -jar Test.jar
unicom 28062 27711 0 22:24 pts/0 00:00:00 grep Test.jar

关闭进程:

[user@data2 test]$ kill -15 28005

查看日志:

[user@data2 test]$ more nohup.out
running ......
…
running ......
TERM is recevied.
running ......

[1]+ Done nohup java -jar Test.jar

附录

在以下场景中需要使用SignalHandler来响应kill信号:

(1)文件操作;

(2)数据库操作;

(3)网络连接;

(4)循环处理的消息、数据记录等。

END