何时应在Java中调用System.exit?

199

在Java中,下面这段代码中加上或不加System.exit(0)有什么区别?

public class TestExit
{      
    public static void main(String[] args)
    { 
        System.out.println("hello world");

        System.exit(0);  // is it necessary? And when it must be called? 
    }      
}

文档中写道: "此方法永远不会正常返回。" 这是什么意思?


请参见https://dev59.com/EV4b5IYBdhLWcg3wvT-w。 - Roland
10个回答

220

System.exit()可以用于在程序退出前运行关闭挂钩。这是处理大型程序中的关闭的方便方式,因为程序的所有部分都不能(也不应)相互了解。然后,如果有人想退出,他只需调用System.exit(),然后关闭挂钩(如果正确设置)会处理所有必要的关闭仪式,例如关闭文件、释放资源等。

"此方法永远不会正常返回"意味着该方法不会返回;一旦一个线程进入此处,则它将不会返回。

另一种可能更常见的退出程序的方法是简单地到达main方法的末尾。但是,如果有任何非守护线程正在运行,则它们不会被关闭,因此JVM将不会退出。因此,如果您有任何这样的非守护线程,您需要一些其他手段(而不是关闭挂钩)来关闭所有非守护线程并释放其他资源。如果没有其他非守护线程,则从main返回将关闭JVM,并调用关闭挂钩。

出于某种原因,关闭挂钩似乎是一种被低估和误解的机制,人们正在使用各种专有的自定义黑客来重新发明轮子来退出他们的程序。我鼓励使用关闭挂钩;它都在您将要使用的标准Runtime中。


12
“这个方法永远不会正常返回。”的意思就是这个方法不会返回,一旦一个线程进入该方法,它就不会回来了。特别要注意的是,这意味着你不能对调用System.exit(0)的方法进行单元测试... - Bill Michell
7
不正确。如果 JVM 正常终止,无论是因为 System.exit 还是 main() 的终止,关闭挂钩都会运行。请参阅 http://zx81/doku/java/javadoc/j2se1.5.0/docs/api/java/lang/Runtime.html#addShutdownHook%28java.lang.Thread%29。 - sleske
32
如果存在其他非守护线程,则main()方法的终止是不够的。除非您显式调用System.exit(),否则关闭仅在最后一个非守护线程终止后启动。这在运行时文档中明确说明。 - Joonas Pulakka
3
请注意,如果您的关闭挂钩依赖于调用System.exit的线程,则会发生死锁。 - djechlin
9
需要翻译的内容:Something to add on, if someone halts a runtime, shutdown hooks will not run (Runtime.getRuntime().halt())如果有人停止了运行时,即使设置了关闭钩子(shutdown hooks),也不会运行它们。具体来说,如果使用 Runtime.getRuntime().halt() 停止运行时,就会发生这种情况。 - Rogue
显示剩余4条评论

49

在这种情况下,这是不必要的。不会启动额外的线程,也不会更改退出代码(默认为0) - 基本上是毫无意义的。

当文档中说该方法永远不会正常返回时,它意味着后续代码行实际上是不可达的,尽管编译器不知道这一点:

System.exit(0);
System.out.println("This line will never be reached");

无论如何,要么会抛出异常,要么虚拟机在返回前将终止。它永远不会“只是返回”。

调用System.exit()非常罕见。如果您正在编写命令行工具,并且希望通过退出代码指示错误而不是只抛出异常,则可能有意义...但我记不起上次在正常生产代码中使用它了。


2
为什么编译器不知道那个?System.exit()不是特殊到足以需要特定检测代码的吗? - Bart van Heukelom
5
@Bart: 不,我不这么认为。在语言中为这样的特殊情况添加特例会增加语言的复杂性,而收益却很少。 - Jon Skeet
好的,解释得很清楚,但JVM是否随时调用它呢?如果不是,那么为什么他们提供了带参数的退出方法?我的意思是用户总是会提供0而不是其他选项,比如1到255。 - Govinda Sakhare
1
@piechuckerr:你为什么这样想?使用非0的值调用程序是完全合理的,以便向调用shell(或其他)指示程序遇到了错误。 - Jon Skeet
我想知道的唯一事情是,在任何情况下,JVM 是否会在内部调用它? - Govinda Sakhare
显示剩余5条评论

15

这个方法永远不会返回,因为这是世界末日,你的所有代码都不会被执行。

在您的示例中,您的应用程序无论如何都将在代码的同一位置退出,但是,如果您使用System.exit,您可以选择向环境返回自定义代码,例如

System.exit(42);

谁会使用你的退出代码?调用该应用程序的脚本。适用于Windows、Unix和所有其他可脚本化环境。

为什么要返回一个代码?用来表示诸如“我没有成功”、“数据库没有响应”之类的信息。

如果想了解如何获取退出代码的值并在Unix shell脚本或Windows cmd脚本中使用它,您可以查看此网站上的这个答案


更好的解释: https://dev59.com/EV4b5IYBdhLWcg3wvT-w - Roland

13

System.exit(0)会终止JVM。在像这样的简单示例中,很难感知到其中的差异。该参数传递回操作系统,并通常用于指示异常终止(例如某种致命错误),因此如果您从批处理文件或shell脚本调用Java,则可以获取此值并了解应用程序是否成功。

如果在部署到应用程序服务器的应用程序上调用System.exit(0),将会产生相当大的影响(在尝试之前,请先考虑一下)。


12
在可能存在复杂关闭钩子的应用程序中,不应从未知线程调用此方法。调用 System.exit 不会正常退出,因为该调用将阻塞,直到 JVM 终止。就好像运行某些代码时突然拔掉电源插头一样,导致其无法完成。调用 System.exit 将启动程序的关闭钩子,并且调用 System.exit 的线程将阻塞,直到程序终止。这意味着,如果关闭钩子反过来向从调用 System.exit 的线程提交任务,则程序将死锁。
在我的代码中,我使用以下方式处理:
public static void exit(final int status) {
    new Thread("App-exit") {
        @Override
        public void run() {
            System.exit(status);
        }
    }.start();
}

我也遇到过System.exit()并不能真正结束JVM的问题,但只发生在特定条件下。我获取的线程转储没有提供任何有关哪些线程/关闭处理程序正在阻止关闭的见解。使用评论中描述的代码帮助我解决了这个问题! - Kai

7
尽管答案非常有帮助,但它似乎缺少更多的细节。我希望下面的内容能够帮助理解Java中的关闭过程,除了上面的答案之外:
  1. 在有序关闭中,JVM首先启动所有已注册的关闭钩子。关闭钩子是未启动的线程,它们是通过Runtime.addShutdownHook进行注册的。
  2. JVM不保证关闭钩子启动的顺序。如果任何应用程序线程(守护进程或非守护进程)在关闭时仍在运行,则它们将与关闭过程同时运行
  3. 当所有关闭钩子完成后,如果runFinalizersOnExit为true,则JVM可能选择运行finalizer,然后停止。
  4. JVM不会尝试停止或中断在关闭时仍在运行的任何应用程序线程;当JVM最终停止时,它们会被突然终止。
  5. 如果关闭钩子或finalizer没有完成,则有序关闭过程“挂起”,JVM必须被突然关闭。
  6. 在突然关闭中,JVM不需要执行任何其他操作,只需停止JVM即可;关闭钩子将不会运行。

PS:JVM可以以有序或突然的方式关闭。

  1. 当最后一个“正常”(非守护)线程终止时,有序关闭被启动,或者通过其他特定于平台的方式(例如发送SIGINT或按Ctrl-C)调用System.exit等方式。
  2. 虽然上述是JVM关闭的标准和首选方式,但也可以通过调用Runtime.halt或通过操作系统(例如发送SIGKILL)杀死JVM进程来突然关闭。

7

以下几点原因说明永远不应该调用System.exit(0)

  1. 这是一种隐藏的“跳转”,而“跳转”会打破控制流。在此情况下,依赖钩子函数需要每个开发人员都清楚地了解。
  2. 正常退出程序将向操作系统提供与System.exit(0)相同的退出代码,因此它是多余的。

    如果您的程序无法正常退出,那么您已经失去了对开发设计的控制。您应始终完全掌控系统状态。

  3. 诸如运行未正常停止的线程等编程问题会变得隐蔽。
  4. 通过异常中断线程,可能会遇到应用程序状态不一致的情况。(参见#3)

顺便说一下:如果您想指示异常程序终止,则返回除0以外的其他返回代码是有意义的。


2
你应该始终对系统状态拥有完全控制。但在Java中,这是不可能的。IoC框架和应用服务器是放弃系统状态控制的两种非常流行和广泛使用的方式。但这完全没问题。 - mhlz
请在您的应用服务器中执行System.exit(0),并告诉我您的服务器管理员的反应... 您错过了这个问题和我的答案的主题。 - oopexpert
也许我没有表达清楚。你应该始终对你负责的系统状态拥有完全控制权。是的,一些责任被转移到了应用服务器和IoC中。但我不是在谈论那个。 - oopexpert

5

需要使用System.exit

  • 当您想返回非0错误代码时
  • 当您想从不是main()的地方退出程序时

在您的情况下,它与简单的从main函数中返回相同。


你说的“后者”是什么意思?关闭程序的正确方式是停止所有线程(优雅地),这个操作不必从主线程启动,所以并不像 exit 是你唯一的选择。 - Bart van Heukelom
@Bart:你所描述的是关闭程序的一种可能方式,而不是正确的方式。使用关闭挂钩(shutdown hooks)优雅地停止所有线程没有任何问题。关闭挂钩是通过exit启动的。这不是唯一的选择,但绝对值得考虑。 - Joonas Pulakka
但从文档中我了解到,关闭钩子非常微妙,可能没有足够的时间来完成您想要的操作。请务必在操作系统关闭或其他情况下使用它们以优雅地退出,但是如果您自己调用System.exit(),我认为最好在其之前执行关闭代码(之后您可能不再需要System.exit())。 - Bart van Heukelom
1
关闭挂钩确实需要完成所有时间。在所有挂钩返回之前,VM不会停止。通过注册需要很长时间执行的关闭挂钩,确实可以防止VM退出。但是,当然可以以各种替代方式执行关闭序列。 - Joonas Pulakka
如果还有其他非守护线程存在,从主函数返回不会关闭程序。 - djechlin

4

Java语言规范指出:

程序退出

当以下两种情况之一发生时,程序将终止其所有活动并退出:

所有非守护线程都已经终止。

某个线程调用了Runtime类或System类的exit方法,并且退出操作未被安全管理器禁止。

这意味着当您有一个大型程序(至少比这个程序大)并且想要结束它的执行时,应该使用它。


3
如果您在JVM中运行另一个程序并使用System.exit,那么第二个程序也将被关闭。例如,假设您在集群节点上运行一个Java作业,并且管理集群节点的Java程序在同一个JVM中运行。如果该作业使用System.exit,则不仅会退出作业,还会“关闭整个节点”。由于管理程序被意外关闭,因此您将无法向该集群节点发送另一个作业。
因此,如果您想能够从JVM内的另一个Java程序控制您的程序,请勿使用System.exit。
如果您想有目的地关闭完整的JVM,并且想利用其他答案中描述的可能性(例如关闭挂钩:Java shutdown hook,命令行调用的非零返回值:How to get the exit status of a Java program in Windows batch file),请使用System.exit。
还要注意运行时异常:System.exit(num) or throw a RuntimeException from main?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接