【问题标题】:How to gracefully handle the SIGKILL signal in Java如何优雅地处理 Java 中的 SIGKILL 信号
【发布时间】:2011-02-02 06:02:33
【问题描述】:

当程序收到终止信号时,您如何处理清理工作?

例如,我连接的一个应用程序希望任何第三方应用程序(我的应用程序)在注销时发送finish 命令。当我的应用被 kill -9 销毁时,发送 finish 命令的最佳说法是什么?

edit 1:kill -9 无法被捕获。谢谢你们纠正我。

edit 2:我想这种情况是当一个调用只是 kill 这与 ctrl-c 相同

【问题讨论】:

  • kill -9 对我来说意味着:“走吧,肮脏的过程,带走你!”,过程将停止。马上。
  • 在我知道的大多数 *nixes 上,kill -9 不能被任何程序拦截和优雅地处理,无论它是用什么语言编写的。
  • @Begui:除了其他人的评论和回答之外,如果您的 Unx 操作系统不会立即杀死 *并重新获得所有资源kill -9'ed 的程序使用,嗯...操作系统坏了。
  • 关于 kill -9 命令,手册页更准确地说:“9 KILL (non-catchable, non-ignorable kill)”。 SIGKILL 由操作系统而不是应用程序处理的信号。
  • 只是kill和Ctrl-C不一样,因为kill没有指定发送哪个信号会发送SIGTERM,而Ctrl-C发送SIGINT。

标签: java sigkill


【解决方案1】:

参考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);
        }
    }
}

【讨论】:

    【解决方案2】:

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

    kill -9 之外的任何其他 的处理方法是注册一个shutdown 挂钩。如果您可以使用 (SIGTERM) kill -15,则关闭挂钩将起作用。 (SIGINT) kill -2 确实会导致程序正常退出并运行关闭挂钩。

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

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

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

    我在 OSX 10.6.3 和 kill -9 上尝试了以下测试程序,它NOT按预期运行了关闭挂钩。在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 上的 TerminateProcess 调用 窗户。

    处理kill -9 的唯一真正选择是让另一个观察程序监视您的主程序消失或使用包装脚本。您可以使用一个轮询ps 命令的shell 脚本来执行此操作,在列表中查找您的程序并在它消失时采取相应措施。

    #!/usr/bin/env bash
    
    java TestShutdownHook
    wait
    # notify your other app that you quit
    echo "TestShutdownHook quit"
    

    【讨论】:

      【解决方案3】:

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

      这样,信号将被转发给它们,允许在standard ways 中优雅地取消线程和资源终结。

      但事实并非如此(至少在我的 JVM 实现中: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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

          种方法可以在某些 JVM 中处理您自己的信号 - 例如,请参阅 this article about the HotSpot JVM

          通过使用 Sun 内部的 sun.misc.Signal.handle(Signal, SignalHandler) 方法调用,您还可以注册信号处理程序,但可能不适用于像 INTTERM 这样的信号,因为它们被 JVM 使用。

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

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

          然后,我会在 JVM 退出时收到通知,以及退出的原因,并可以采取必要的措施。

          【讨论】:

          • 注意你可以sun.misc.Signal捕获INTTERM,但是你不能处理QUIT,因为JVM保留它用于调试,也不能@ 987654330@ 因为操作系统会立即终止 JVM。尝试处理任何一个都会引发IllegalArgumentException
          【解决方案6】:

          你可以使用Runtime.getRuntime().addShutdownHook(...),但你不能保证它在任何情况下都会被调用

          【讨论】:

          • 但是在 kill -9 的情况下它几乎肯定不会运行。
          猜你喜欢
          • 1970-01-01
          • 2023-04-02
          • 1970-01-01
          • 2016-01-19
          • 2011-04-23
          • 1970-01-01
          • 2013-02-25
          • 2020-03-19
          相关资源
          最近更新 更多