程序接收到kill信号时如何进行清除操作?
例如,我连接的一个应用程序希望任何第三方应用程序(我的应用程序)在注销时发送finish
命令。当我的应用程序被使用kill -9
销毁时,最好的方法是发送哪个finish
命令?
编辑1:无法捕获kill -9。谢谢大家指正。
编辑2:我想这种情况可能是当一个人只调用了“kill”(与ctrl-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);
}
}
}
在任何程序中,没有真正优雅地处理kill -9
的方法。
在极少数情况下,虚拟机可能会中止,即在不干净关闭的情况下停止运行。当虚拟机在外部终止时,例如在Unix上使用SIGKILL信号或在Microsoft Windows上使用TerminateProcess调用时,就会发生这种情况。
唯一真正处理kill -9
的选择是使用另一个监视程序来监视您的主程序是否消失,或者使用包装脚本。您可以使用轮询ps
命令查找程序列表并在其消失时相应地进行操作。
#!/usr/bin/env bash
java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"
thread.interrupt()
)应用程序创建的所有运行线程,至少对于SIGINT (kill -2)
和SIGTERM (kill -15)
信号。这样,信号将被转发到它们,允许在标准方式中进行线程取消和资源终止。Java(TM) SE Runtime Environment (build 1.8.0_25-b17), Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
)。正如其他用户所评论的那样,使用shutdown hooks似乎是强制性的。
那么,我该如何处理它呢?
首先,我并不关心所有程序中的它,只在我想跟踪用户取消和意外结束的程序中关注它。例如,假设您的Java程序是由其他进程管理的进程。您可能希望区分它是否已经优雅地终止(来自管理进程的SIGTERM),或者是否发生了关闭(以便在启动时自动重新启动作业)。
作为基础,我总是让我的长时间运行的线程周期性地知道被中断的状态,并在被中断时抛出InterruptedException。这使得执行终止由开发人员控制(也产生与标准阻塞操作相同的结果)。然后,在线程堆栈的顶层,捕获InterruptedException并执行适当的清理。这些线程编码为知道如何响应中断请求。高cohesion设计。
因此,在这些情况下,我添加了一个关机钩子,它执行我认为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
在某些JVM中,有办法处理自己的信号--请参见关于HotSpot JVM的这篇文章。
通过使用Sun内部的sun.misc.Signal.handle(Signal, SignalHandler)
方法调用,您还可以注册信号处理程序,但可能无法处理像INT
或TERM
这样由JVM使用的信号。
要能够处理任何信号,您必须跳出JVM并进入操作系统领域。
通常我所做的(例如)是在Perl脚本中启动我的JVM,但通过使用waitpid
系统调用使脚本等待JVM。
然后我会被告知JVM何时退出以及退出的原因,并采取必要的措施。
sun.misc.Signal
捕获INT
和TERM
信号,但无法处理QUIT
信号,因为JVM将其保留用于调试,也无法处理KILL
信号,因为操作系统会立即终止JVM。尝试处理任何一个信号都会引发IllegalArgumentException
异常。 - dimo414Runtime.getRuntime().addShutdownHook(...)
,但不能保证它会在任何情况下都被调用。参考 https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/
本文介绍了在 Amazon Elastic Container Service (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);
}
}
}
对于kill -9的一种应对方式是,有一个单独的进程来监视被杀死的进程,并在必要时进行清理。这可能涉及IPC并且需要相当多的工作,而且您仍然可以通过同时杀死两个进程来覆盖它。我认为,在大多数情况下,这将不值得麻烦。
使用-9杀死进程的人理论上应该知道自己在做什么,并且可能会使事情处于不一致状态。
kill -9
对我来说意味着:“去吧,邪恶的进程,离开这里!” 这时进程将立即停止。 - ZoogieZork