29

我有一个 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 也是如此。

4

9 回答 9

18

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

一个更有力的(所以应该只在极端情况下使用!)强制关机的方法是运行:

Runtime.getRuntime().halt(0);
于 2017-05-10T10:53:36.577 回答
7

父进程有一个线程专门用于使用子进程的 STDOUT 和 STDERR(将输出传递到日志文件)。据我所知,这些工作正常,因为我们看到了我们希望在日志中看到的所有输出

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

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

于 2010-09-12T22:25:30.077 回答
5

检查是否存在死锁。

例如,

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

PS:我觉得上面的exit()版本实际上必须只写在System.exit()方法中(可能是JDK的一些PR?)因为,实际上没有意义(至少从我所见)中娱乐死锁System.exit()

于 2019-08-16T05:00:15.303 回答
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。

于 2010-04-10T21:41:06.897 回答
1

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

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

您确定孩子仍然在运行并且它不仅仅是一个未收获的僵尸进程吗?

于 2010-04-10T20:11:58.233 回答
1

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

于 2010-04-10T21:36:14.343 回答
1

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

于 2016-06-22T09:04:57.903 回答
0

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

建议:

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

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

于 2010-04-11T04:12:01.743 回答
0

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

院长

于 2013-01-28T19:31:51.717 回答