如何在 Java 中优雅地处理 SIGKILL 信号

当程序接收到终止信号时,如何处理清除?

For instance, there is an application I connect to that wants any third party app (my app) to send a finish command when logging out. What is the best say to send that finish command when my app has been destroyed with a kill -9?

编辑1: kill-9不能被捕获。谢谢你们纠正我。

编辑2: 我猜想这种情况是当一个调用刚刚杀死,这是相同的 ctrl-c

124671 次浏览

对于任何语言的任何程序来说,处理 SIGKILL 都是 不可能。因此,即使程序存在漏洞或恶意,也始终可以终止程序。但 SIGKILL 并不是终止程序的唯一方法。另一种是使用 SIGTERM。程序 可以处理那个信号。程序 应该通过执行一个受控但快速的关闭来处理信号。当一台计算机关闭时,关闭过程的最后一个阶段向每个剩余的进程发送一个 SIGTERM,给这些进程几秒钟的宽限时间,然后向它们发送一个 SIGKILL。

处理 其他而不是 kill -9的方法是注册一个 关闭挂钩。如果你可以使用(SIGTERM) kill -15关机钩将工作。(信号) kill -2 是的使程序优雅地退出并运行关闭挂钩。

注册一个新的虚拟机关机钩子。

Java 虚拟机在响应两种事件时关闭:

  • 当最后一个非守护进程线程退出或调用 exit (相当于 System.exit)方法时,程序正常退出,或者
  • 虚拟机在响应用户中断(如键入 ^ C)或系统范围的事件(如用户注销或系统关闭)时终止。

我在 OSX 10.6.3上尝试了下面的测试程序,在 kill -9上,它按照预期的那样运行了 没有关机挂钩。在一个 kill -15是的运行关闭钩每次。

public class TestShutdownHook
{
public static void main(String[] args) throws InterruptedException
{
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
System.out.println("Shutdown hook ran!");
}
});


while (true)
{
Thread.sleep(1000);
}
}
}

There isn't any way to really gracefully handle a kill -9 in any program.

在极少数情况下 机器可能中止,也就是停止 没有干净地停止运行。 This occurs when the virtual machine 例如,从外部终止 with the SIGKILL signal on Unix or the TerminateProcess call on Microsoft 窗户。

处理 kill -9的唯一真正选择是让另一个监视程序监视你的主程序,让它离开或者使用包装器脚本。您可以使用一个 shell 脚本来做到这一点,该脚本轮询 ps命令,在列表中查找您的程序,并在程序消失时相应地执行操作。

#!/usr/bin/env bash


java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"

您可以使用 Runtime.getRuntime().addShutdownHook(...),但是不能保证它将被称为 无论如何

在某些 JVM 中,有 处理自己的信号的方法——例如,请参见 这篇关于 HotSpot JVM 的文章

通过使用 Sun 内部的 sun.misc.Signal.handle(Signal, SignalHandler)方法调用,您还能够注册一个信号处理程序,但是对于诸如 INTTERM这样的信号可能不能,因为它们是由 JVM 使用的。

为了能够处理 any信号,您必须跳出 JVM,进入操作系统领域。

我通常(例如)检测异常终止的方法是在 Perl 脚本中启动 JVM,但是让脚本使用 waitpid系统调用等待 JVM。

然后,每当 JVM 退出时,我都会被告知它为什么退出,并且可以采取必要的操作。

我希望 JVM 能够优雅地 打断(thread.interrupt())应用程序创建的所有正在运行的线程,至少对于信号 SIGINT (kill -2)SIGTERM (kill -15)是如此。

这样,信号将被转发给它们,允许在 标准方法中优雅地取消线程和完成资源。

But this is not the case (at least in my JVM implementation: Java(TM) SE Runtime Environment (build 1.8.0_25-b17), Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode).

正如其他用户所评论的,使用 关闭挂钩似乎是强制性的。

那我该怎么处理呢?

首先,我不关心它在所有的程序,只有在那些我想跟踪用户取消和意外结束。例如,假设您的 java 程序是由其他。您可能想要区分它是否已经优雅地终止(来自管理器进程的 SIGTERM)或者已经发生了关闭(以便在启动时自动重新启动作业)。

作为基础,我总是让长时间运行的线程周期性地知道被中断的状态,并在它们被中断时抛出 InterruptedException。这使得执行结束由开发人员控制(也产生与标准阻塞操作相同的结果)。然后,在线程堆栈的顶层捕获 InterruptedException并执行适当的清理。这些线程被编码为知道如何响应中断请求。高 凝聚力设计。

因此,在这些情况下,我添加了一个关闭钩子,它完成了我认为 JVM 默认情况下应该完成的任务: 中断应用程序创建的所有仍在运行的非守护进程线程:

Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("Interrupting threads");
Set<Thread> runningThreads = Thread.getAllStackTraces().keySet();
for (Thread th : runningThreads) {
if (th != Thread.currentThread()
&& !th.isDaemon()
&& th.getClass().getName().startsWith("org.brutusin")) {
System.out.println("Interrupting '" + th.getClass() + "' termination");
th.interrupt();
}
}
for (Thread th : runningThreads) {
try {
if (th != Thread.currentThread()
&& !th.isDaemon()
&& th.isInterrupted()) {
System.out.println("Waiting '" + th.getName() + "' termination");
th.join();
}
} catch (InterruptedException ex) {
System.out.println("Shutdown interrupted");
}
}
System.out.println("Shutdown finished");
}
});

在 github 上完成测试应用程序: https://github.com/idelvall/kill-test

对 kill-9的反应有一种方式: 那就是有一个单独的进程来监视被杀死的进程,并在必要时清理它。这可能涉及到 IPC,并且工作量很大,您仍然可以通过同时杀死这两个进程来覆盖它。我想在大多数情况下不值得这么麻烦。

理论上,使用 -9终止进程的人应该知道他/她正在做什么,并且可能会使事情处于不一致的状态。

Reference https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/

import sun.misc.Signal;
import sun.misc.SignalHandler;
 

public class ExampleSignalHandler {
public static void main(String... args) throws InterruptedException {
final long start = System.nanoTime();
Signal.handle(new Signal("TERM"), new SignalHandler() {
public void handle(Signal sig) {
System.out.format("\nProgram execution took %f seconds\n", (System.nanoTime() - start) / 1e9f);
System.exit(0);
}
});
int counter = 0;
while(true) {
System.out.println(counter++);
Thread.sleep(500);
}
}
}