是什么导致Java在System.exit()之后继续运行?

时间:2010-04-10 20:04:03

标签: java exit processbuilder

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

有人可以说明为什么JVM在System.exit()之后无法终止的原因,然后只能在某些机器上终止?

编辑1:我让用户安装JDK,这样我们就可以从违规的虚拟机中获取线程转储。用户告诉我的是,一旦他点击我的菜单中的“退出”项目,VM进程就会从VisualVM中消失---但是,根据Windows任务管理器,该进程尚未终止,无论多长时间用户等待(分钟,小时),它永远不会终止。

编辑2:我现在已经确认父程序中的System.exit()永远不会为至少一个有问题的用户返回。总而言之:孩子VM似乎已经死了(VisualVM甚至没有看到它),但是父母仍然认为这个过程是实时的,Windows也是如此。

9 个答案:

答案 0 :(得分:7)

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

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

Runtime.getRuntime().halt(0);

答案 1 :(得分:6)

  

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

当我使用stdout / stderr时,我的程序没有从任务mgr中消失,我遇到了类似的问题。在我的情况下,如果我在调用system.exit()之前关闭了正在侦听的流,那么javaw.exe就会挂起。奇怪的是,它没有写入流......

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

答案 2 :(得分:4)

以下是几个场景......

根据http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Thread.html

中的线程定义

...

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

1)已调用类Runtime的exit方法,安全管理器已允许进行退出操作。 2)所有非守护程序线程的线程都已经死亡,无论是通过调用run方法返回还是抛出一个超出run方法传播的异常。

另一种可能性是调用了runFinalizersOnExit方法。根据{{​​3}}中的文档 已过时。这种方法本质上是不安全的。它可能导致在活动对象上调用终结器,而其他线程同时操作这些对象,从而导致不稳定的行为或死锁。 退出时启用或禁用完成;这样做指定在Java运行时退出之前运行具有尚未自动调用的终结器的所有对象的终结器。默认情况下,退出时终止已禁用。 如果有安全管理器,则首先调用其checkExit方法,并将0作为其参数,以确保允许退出。这可能会导致SecurityException。

答案 3 :(得分:1)

也许是一个写得不好的终结者?当我阅读主题行时,我首先想到了一个关机钩子。推测:一个捕获InterruptedException并继续运行的线程会阻止退出过程吗?

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

你确定这个孩子还在真正跑步吗?这不仅仅是一个没有生命的僵尸过程吗?

答案 4 :(得分:1)

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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

检查是否存在死锁。

例如,

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

close()

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

System.exit()实际上调用了shutdown钩子和终结器。因此,如果关闭钩子出于某种原因在内部再次调用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()

中陷入僵局

答案 7 :(得分:0)

我认为所有明显的原因都已暂时涵盖;例如终结器,关闭挂钩,没有正确排除父进程中的标准输出/标准错误。您现在需要更多证据来确定正在发生的事情。

建议:

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

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

答案 8 :(得分:0)

这里没有提到的另一个案例是,如果关闭钩子挂起,那么在编写关闭钩子代码时要小心(如果第三方库注册了一个挂起的关闭钩子)。

迪安