Java的System.exit()方法和try/catch/finally代码块如何配合使用?

90

我知道在try/catch/finally块中使用return会产生麻烦 - 即使try或catch块中有返回语句,但最终块中的返回语句仍然是该方法的返回语句。

那么System.exit()也是如此吗?例如,如果我有一个try块:

try {
    //Code
    System.exit(0)
}
catch (Exception ex) {
    //Log the exception
}
finally {
    System.exit(1)
}
如果没有异常,那么将调用哪个System.exit()呢?如果退出是一个返回语句,那么就会始终调用System.exit(1)这一行代码。但是,我不确定exit和return的行为是否有所不同。
代码处于一个非常困难,甚至可能无法复制的极端情况下,所以我无法编写单元测试。如果我今天稍微有点空闲时间,我将尝试运行一个实验,但无论如何,我都很好奇,也许SO上的某个人知道答案并可以在我无法运行实验之前或之后提供答案。

10
这是一个难以测试的极端案例,为什么会这样呢? - JRL
2
我提供的代码不是关于这个的。然而,这是为了争取时间写测试(现在不再需要,多亏了erickson)。 - Thomas Owens
回答你所提出的确切问题需要额外添加一行代码进行测试:在 System.exit(0) 之前加入 throw new Exception("Error");,然后编译并查看可执行文件返回的状态码。 - Mouradif
6个回答

88

不,System.exit(0)不会返回,并且finally块不会被执行。

System.exit(int)可能会抛出SecurityException。如果发生这种情况,则finally块将被执行。由于相同的主体从相同的代码库调用了相同的方法,因此第二次调用很可能会抛出另一个SecurityException


以下是第二种情况的示例:

import java.security.Permission;

public class Main
{

  public static void main(String... argv)
    throws Exception
  {
    System.setSecurityManager(new SecurityManager() {

      @Override
      public void checkPermission(Permission perm)
      {
        /* Allow everything else. */
      }

      @Override
      public void checkExit(int status)
      {
        /* Don't allow exit with any status code. */
        throw new SecurityException();
      }

    });
    System.err.println("I'm dying!");
    try {
      System.exit(0);
    } finally {
      System.err.println("I'm not dead yet!");
      System.exit(1);
    }
  }

}

16
如果调用 System.exit 的栈帧恰好在栈限制之前,那么 StackOverflowException 也有可能阻止 System.exit 的发生。这种边界条件有时可以被黑客所利用,因此如果您的安全不变量依赖于此,请安装一个 Thread.UncaughtExceptionHandler 作为深度防御退出。 - Mike Samuel
1
只是补充一下,我经常遇到这样的情况:如果Ant使用Ant的java任务作为应用程序启动脚本,则OP在问题中提供的相同代码块将导致执行finally块()或不执行(如果应用程序直接使用java从控制台调用而不使用Ant)。我猜根据这个答案,当应用程序从Ant的java任务中调用时,会抛出SecurityException - Marcus Junius Brutus
1
StackOverflowException不存在(除非您定义一个)。它是StackOverflowError。这是一个重要的小区别。 - Peter Verhas
Java 对 System.exit 的处理是一个设计错误,我认为 Python 处理得更好。 - Dávid Horváth
有没有什么可以替代System.exit()的方法,使finally块得以执行? - ed22
@ed22 我猜你的意思是你不想简单地删除对 exit() 的调用,而是仍然希望在退出前无条件执行代码。如果是这样的话,你可以注册一个包含finally块中代码的关闭钩子函数(shutdown hook)。不过,一个好的解决方案高度依赖于具体的代码情况,所以最好发布一个具体的问题。 - erickson

11

简单的测试,包括使用 catch,都表明如果 system.exit(0) 不会抛出安全异常,它将是最后执行的语句(catchfinally 根本不会被执行)。

如果 system.exit(0) 抛出安全异常,则执行 catchfinally 语句。如果 catchfinally 都包含 system.exit() 语句,则只执行这些语句之前的语句。

在上述两种情况下,如果 try 代码属于由另一个方法调用的方法,则调用的方法不会返回。

更多详细信息请参考此处(个人博客)。


3
谢谢您负责署名的事情。 :) - Andrew Barber

8
其他答案已经解释了如果System.exit在不抛出SecurityException的情况下退出JVM,则catchfinally块不会运行,但他们并没有展示在“try-with-resources”块中资源会发生什么:它们会被关闭吗?
根据JLS第14.20.3.2节的规定:
翻译的效果是将资源规范“放入”try语句中。这允许扩展的try-with-resources语句的catch子句捕获由于任何资源的自动初始化或关闭而引发的异常。
此外,所有资源都将在执行finally块时关闭(或尝试关闭),以符合finally关键字的意图。
也就是说,在catchfinally块运行之前,资源将被关闭。即使catchfinally块不运行,资源也会被关闭。
以下是演示“try-with-resources”语句中资源未被关闭的一些代码。
我使用BufferedReader的简单子类,在调用super.close之前打印一个语句。
class TestBufferedReader extends BufferedReader {
    public TestBufferedReader(Reader r) {
        super(r);
    }

    @Override
    public void close() throws IOException {
        System.out.println("close!");
        super.close();
    }
}

接下来我设置了一个测试用例,在try-with-resources语句中调用System.exit

public static void main(String[] args)
{
    try (BufferedReader reader = new TestBufferedReader(new InputStreamReader(System.in)))
    {
        System.out.println("In try");
        System.exit(0);
    }
    catch (Exception e)
    {
        System.out.println("Exception of type " + e.getClass().getName() + " caught: " + e.getMessage());
    }
    finally
    {
        System.out.println("finally!");
    }
}

输出:

在try块中

因此,不仅catchfinally块不会运行,如果System.exit执行成功,"try-with-resources"语句也不会有机会对其资源进行close操作。


4
无论如何,最终块都会被执行...即使尝试块抛出任何可抛出对象(异常或错误)...
唯一的情况是,当我们调用System.exit()方法时,最终块不会执行...
try{
    System.out.println("I am in try block");
    System.exit(1);
} catch(Exception ex){
    ex.printStackTrace();
} finally {
    System.out.println("I am in finally block!!!");
}

它将不会执行finally块。程序将在System.exit()语句之后终止。


3
如果您认为这种行为有问题,并且需要精细地控制System.exit调用,那么您唯一能做的就是在自己的逻辑中包装System.exit功能。如果我们这样做,我们可以使finally块得到执行并作为退出流程的一部分关闭资源。
我考虑的是将System.exit调用和功能包装在我的自己的静态方法中。在我的exit实现中,我会抛出一个自定义的ThrowableError子类,并使用Thread.setDefaultUncaughtExceptionHandler实现一个自定义未捕获异常处理程序来处理该异常。因此,我的代码变成了:
//in initialization logic:
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
  if(exception instanceof SystemExitEvent){
    System.exit(((SystemExitEvent)exception).exitCode);
  }
})

// in "main flow" or "close button" or whatever
public void mainFlow(){
  try {
    businessLogic();
    Utilities.exit(0);
  }
  finally {
    cleanUpFileSystemOrDatabaseConnectionOrWhatever();  
  }
}

//...
class Utilities {

  // I'm not a fan of documentaiton, 
  // but this method could use it.
  public void exit(int exitCode){
    throw new SystemExitEvent(exitCode);
  }
}

class SystemExitEvent extends Throwable { 
  private final int exitCode;

  public SystemExitEvent(int exitCode){
    super("system is shutting down")
    this.exitCode = exitCode;
  }
} 

这种策略的好处在于它使这个逻辑可测试:要测试包含我们“主流程”的方法是否实际请求系统退出,我们只需要捕获一个throwable并断言它是正确的类型即可。例如,我们业务逻辑包装器的一个测试可能如下所示:

//kotlin, a really nice language particularly for testing on the JVM!

@Test fun `when calling business logic should business the business`(){
  //setup
  val underTest = makeComponentUnderTest(configureToReturnExitCode = 42);

  //act
  val thrown: SystemExitEvent = try {
    underTest.mainFlow();
    fail("System Exit event not thrown!")
  }
  catch(event: SystemExitEvent){
    event;
  }

  //assert
  assertThat(thrown.exitCode).isEqualTo(42)

这种策略的主要缺点是它是从异常流程中获取功能的一种方式,这经常会产生意想不到的后果。在这种情况下,最明显的一个后果就是,任何你写过类似于try { ... } catch(Throwable ex){ /*doesnt rethrow*/ } 的代码都需要更新。对于具有自定义执行上下文的库,在引入这种异常处理之前,它们也需要进行改造。总体而言,我认为这是一种不错的策略。还有其他人也这样认为吗?

0
  1. 在下面的示例中,如果System.exit(0)在异常行之前,则程序将正常终止,因此FINALLY将不会执行。

  2. 如果System.exix(0)是try块的最后一行,则有两种情况

    • 当存在异常时,finally块将被执行
    • 当不存在异常时,finally块将不被执行

.

package com.exception;

public class UserDefind extends Exception {
private static int accno[] = {1001,1002,1003,1004,1005};

private static String name[] = {"raju","ramu","gopi","baby","bunny"};

private static double bal[] = {9000.00,5675.27,3000.00,1999.00,1600.00};
UserDefind(){}

UserDefind(String str){
    super(str);
}


public static void main(String[] args) {
    try {
        //System.exit(0); -------------LINE 1---------------------------------
        System.out.println("accno"+"\t"+"name"+"\t"+"balance");

        for (int i = 0; i < 5; i++) {
            System.out.println(accno[i]+"\t"+name[i]+"\t"+bal[i]);
            //rise exception if balance < 2000
            if (bal[i] < 200) {
                UserDefind ue = new UserDefind("Balance amount Less");
                throw ue;
            }//end if
        }//end for
        //System.exit(0);-------------LINE 2---------------------------------

    }//end try
    catch (UserDefind ue)
    {
        System.out.println(ue);
    }
    finally{
        System.out.println("Finnaly");
        System.out.println("Finnaly");
        System.out.println("Finnaly");
    }
}//end of main

}//end of class

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