如何优雅地停止Java进程 (2)

实际上,大多数情况下的进程结束操作通常是在进程运行过程中需要停止进程或者重启进程,而不是等待进程自己运行结束(服务程序都是一直运行的,并不会主动结束)。也就是说,针对JVM正常关闭的情形,大多数情况是使用kill -15 进程ID的方式实现的。那么,我们是否可以结合操作系统的信号量机制和JVM的关闭钩子实现优雅地关闭Java进程呢?答案是肯定的,具体实现步骤如下:

第一步:在应用程序中监听信号量
由于不通的操作系统类型实现的信号量动作存在差异,所以监听的信号量需要根据Java进程实际运行的环境而定(如:Windows使用SIGINT,Linux使用SIGTERM)

Signal sg = new Signal("TERM"); // kill -15 pid Signal.handle(sg, new SignalHandler() { @Override public void handle(Signal signal) { System.out.println("signal handle: " + signal.getName()); // 监听信号量,通过System.exit(0)正常关闭JVM,触发关闭钩子执行收尾工作 System.exit(0); } });

第二步:注册关闭钩子

Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run() { // 执行进程退出前的工作 // 注意事项: // 1.在这里执行的动作不能耗时太久 // 2.不能在这里再执行注册,移除关闭钩子的操作 // 3 不能在这里调用System.exit() System.out.println("do something"); } });

完整示例如下:

public class ShutdownTest { public static void main(String[] args) { System.out.println("Shutdown Test"); Signal sg = new Signal("TERM"); // kill -15 pid // 监听信号量 Signal.handle(sg, new SignalHandler() { @Override public void handle(Signal signal) { System.out.println("signal handle: " + signal.getName()); System.exit(0); } }); // 注册关闭钩子 Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run() { // 在关闭钩子中执行收尾工作 // 注意事项: // 1.在这里执行的动作不能耗时太久 // 2.不能在这里再执行注册,移除关闭钩子的操作 // 3 不能在这里调用System.exit() System.out.println("do shutdown hook"); } }); mockWork(); System.out.println("Done."); System.exit(0); } // 模拟进程正在运行 private static void mockWork() { //mockRuntimeException(); //mockOOM(); try { Thread.sleep(120 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } // 模拟在应用中抛出RuntimeException时会调用注册钩子 private static void mockRuntimeException() { throw new RuntimeException("This is a mock runtime ex"); } // 模拟应用运行出现OOM时会调用注册钩子 // -xms10m -xmx10m private static void mockOOM() { List list = new ArrayList(); for(int i = 0; i < 1000000; i++) { list.add(new Object()); } } } 总结

网上有文章总结说可以直接使用监听信号量的机制来实现优雅地关闭Java进程(详见:

Java程序优雅关闭的两种方法

,实际上这是有问题的。因为单纯地监听信号量,并不能覆盖到异常关闭JVM的情形(如:RuntimeException或OOM),这种方式与注册关闭钩子的区别在于:
1.关闭钩子是在独立线程中运行的,当应用进程被kill的时候main函数就已经结束了,仅会运行ShutdownHook线程中run()方法的代码。
2.监听信号量方法中handle函数会在进程被kill时收到TERM信号,但对main函数的运行不会有任何影响,需要使用别的方式结束main函数(如:在main函数中添加布尔类型的flag,当收到TERM信号时修改该flag,程序便会正常结束;或者在handle函数中调用System.exit())。

【参考】
https://blog.csdn.net/u011001084/article/details/73480432 JVM安全退出(如何优雅的关闭java服务)
Java保证程序结束时调用释放资源函数
https://tessykandy.iteye.com/blog/2005767 基于kill信号优雅的关闭JAVA程序
https://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html Linux 信号signal处理机制

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdfgw.html