【问题标题】:What can cause Java to keep running after System.exit()?什么会导致 Java 在 System.exit() 之后继续运行?
【发布时间】:2010-04-10 20:04:02
【问题描述】:

我有一个 Java 程序正在通过 ProcessBuilder 从另一个 Java 程序启动。 System.exit(0) 是从子程序调用的,但对于我们的一些用户(在 Windows 上),与子程序关联的 java.exe 进程不会终止。子程序没有关闭挂钩,也没有SecurityManager 可能会阻止System.exit() 终止VM。我自己无法在 Linux 或 Windows Vista 上重现该问题。到目前为止,该问题的唯一报告来自两名 Windows XP 用户和一名 Vista 用户,他们使用两种不同的 JRE(1.6.0_15 和 1.6.0_18),但他们每次都能重现该问题。

谁能指出 JVM 在System.exit() 之后无法终止的原因,然后仅在某些机器上?

编辑 1: 我让用户安装 JDK,以便我们可以从有问题的 VM 中获取线程转储。用户告诉我的是,只要他单击菜单中的“退出”项,VM 进程就会从 VisualVM 中消失 --- 但是,根据 Windows 任务管理器,该进程并没有终止,无论多长时间用户等待(分钟,小时),它永远不会终止。

编辑 2: 我现在已经确认父程序中的 Process.waitFor() 永远不会为至少一个有问题的用户返回。所以,总结一下:子虚拟机似乎已经死了(VisualVM 甚至看不到它),但父虚拟机仍然认为该进程是活动的,Windows 也是如此。

【问题讨论】:

    标签: java exit processbuilder


    【解决方案1】:

    如果您的代码(或您使用的库)有一个关闭挂钩或终结器无法顺利完成,则可能会发生这种情况。

    一种更有效(因此只应在极端情况下使用!)强制关机的方法是运行:

    Runtime.getRuntime().halt(0);
    

    【讨论】:

      【解决方案2】:

      父进程有一个线程 致力于消费每一个 孩子的 STDOUT 和 STDERR(其中 将该输出传递到日志 文件)。据我所知,这些是 工作正常,因为我们看到 我们期望在 日志

      当我使用标准输出/标准错误时,我的程序没有从任务管理器中消失也有类似的问题。就我而言,如果我在调用 system.exit() 之前关闭了正在监听的流,那么 javaw.exe 就会挂起。奇怪,它没有写入流...

      在我的情况下,解决方案是简单地刷新流而不是在存在之前关闭它。当然,您可以随时刷新,然后在退出前重定向回 stdout 和 stderr。

      【讨论】:

        【解决方案3】:

        检查是否存在死锁。

        例如,

        Runtime.getRuntime().addShutdownHook(new Thread(this::close));
        System.exit(1);
        

        close()

        public void close() {
        // some code
        System.exit(1);
        }
        

        System.exit() 实际上调用了关闭钩子和终结器。因此,如果您的关闭挂钩由于某种原因在内部再次调用 exit()(例如,当 close() 引发您想要退出程序的异常时,那么您也将在那里调用 exit())。像这样..

        public void close() {
          try {
          // some code that raises exception which requires to exit the program
          } catch(Exception exceptionWhichWhenOccurredShouldExitProgram) {
            log.error(exceptionWhichWhenOccurredShouldExitProgram);
            System.exit(1);
          }
        }
        

        虽然抛出异常是一个好习惯,但有些人可能会选择记录并退出。

        另外请注意,如果出现死锁,Ctrl+C 也将不起作用。 因为它也调用了关闭钩子。

        无论如何,如果是这种情况,问题可以通过这种解决方法解决:

        private static AtomicBoolean exitCalled=new AtomicBoolean();
        
            private static void exit(int status) {
                if(!exitCalled.get()) {
                    exitCalled.set(true);
                    System.exit(status);
                }
            }
        
            Runtime.getRuntime().addShutdownHook(new Thread(MyClass::close));
              exit(1);
            }
        
            private static void close() {
                exit(1);
            }
        

        P.S: 感觉上面的exit()版本其实一定要写 仅在 System.exit() 方法中(可能是 JDK 的一些 PR?)因为, 几乎没有意义(至少从我所见) 在System.exit()中解决僵局

        【讨论】:

          【解决方案4】:

          这里有几个场景...

          根据http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Thread.html中的线程定义

          ...

          当 Java 虚拟机启动时,通常有一个非守护线程(通常调用某个指定类的名为 main 的方法)。 Java 虚拟机继续执行线程,直到发生以下任一情况:

          1) 已调用 Runtime 类的退出方法,并且安全管理器已允许进行退出操作。 2) 所有不是守护线程的线程都已经死亡,要么是从调用 run 方法返回,要么是抛出一个传播到 run 方法之外的异常。

          另一种可能性是方法 runFinalizersOnExit 是否已被调用。根据http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html 中的文档 已弃用。这种方法本质上是不安全的。这可能会导致在其他线程同时操作这些对象时对活动对象调用终结器,从而导致行为不稳定或死锁。 在退出时启用或禁用终结;这样做指定在 Java 运行时退出之前运行具有尚未自动调用的终结器的所有对象的终结器。默认情况下,退出时的最终确定是禁用的。 如果有安全管理器,则首先调用其 checkExit 方法,并以 0 作为其参数,以确保允许退出。这可能会导致 SecurityException。

          【讨论】:

          • 我们没有给runFinalizersOnExit打电话,也没有SecurityManager,所以我认为这些不是原因。
          【解决方案5】:

          也许是一个写得很糟糕的终结器?当我阅读主题行时,我首先想到的是关闭挂钩。推测:捕获 InterruptedException 并继续运行的线程是否会阻止退出进程?

          在我看来,如果问题是可重现的,您应该能够附加到 JVM 并获得一个线程列表/堆栈跟踪,以显示挂起的内容。

          你确定这个子进程真的还在运行,而且它不仅仅是一个未收割的僵尸进程吗?

          【讨论】:

          • 如果它是一个终结器,那么它就不是我们代码中的终结器——我们没有。我确定孩子仍在运行,因为我们看到两个 Java 进程并且用户在他的系统上没有任何其他使用 Java 的程序。我有点希望避免让他安装 JDK 以便我们可以使用 jstack,但我认为现在这可能是最好的方法。
          • 子进程的意思是,它还有线程吗?它仍然响应输入吗?它还有打开的把手吗?我对 Unix 比对 Windows 更熟悉,但我知道在父进程获取它们之前,不会从 Unix 操作系统中清除进程,我认为在 Windows 上也是如此。仅仅因为它显示在顶部或 TaskManager 中并不意味着它仍然处于活动状态。当然,这并不能解释为什么它只发生在某些用户身上......
          • 默认情况下,终结器不会在退出时运行;见java.sun.com/javase/6/docs/api/java/lang/…
          【解决方案6】:

          父进程是否使用子进程的错误和输出流? 如果在某些操作系统下子进程在 stdout/stderr 上打印出一些错误/警告并且父进程没有消耗流,则子进程将阻塞并且无法到达 System.exit();

          【讨论】:

          • 父进程有一个线程专门用于消耗子进程的 STDOUT 和 STDERR(将输出传递到日志文件)。据我所知,这些都正常工作,因为我们看到了我们希望在日志中看到的所有输出。
          【解决方案7】:

          我有同样的问题,但对我来说,我在使用远程调试(VmArgs: -Xdebug -Xrunjdwp:transport=dt_socket,address=%port%,server=y,suspend=y)时禁用此 java.exe 进程按预期退出。

          【讨论】:

            【解决方案8】:

            我认为所有明显的原因都已暂时涵盖;例如终结器、关闭挂钩、未正确排出父进程中的标准输出/标准错误。您现在需要更多证据来弄清楚发生了什么。

            建议:

            1. 设置 Windows XP 或 Vista 机器(或虚拟机),安装相关的 JRE 和您的应用程序,并尝试重现问题。一旦您可以重现问题,请附加调试器或发送相关信号以将线程转储到标准错误。

            2. 如果您无法重现上述问题,请让您的一位用户进行线程转储,并将日志文件转发给您。

            【讨论】:

            • 我自己无法在 Vista 上重现它,并且用户无法从 VisualVM 获取线程转储,因为 VM 似乎不再存在。
            【解决方案9】:

            这里没有提到的另一种情况是,如果关闭挂钩挂起,因此在编写关闭挂钩代码时要小心(并且如果第 3 方库注册了一个挂起的关闭挂钩)。

            院长

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-09-01
              • 1970-01-01
              • 2018-07-12
              • 2013-08-26
              • 1970-01-01
              • 2019-01-02
              • 1970-01-01
              • 2017-04-21
              相关资源
              最近更新 更多