Java的System.exit()如何与try / catch / finally块一起使用?

How does Java's System.exit() work with try/catch/finally blocks?

本问题已经有最佳答案,请猛点这里访问。

我知道在try/catch/finally块中返回会带来一些头痛——在这种情况下,finally中的返回始终是方法的返回,即使try或catch块中的返回应该是执行的返回。

但是,这同样适用于system.exit()吗?例如,如果我有一个try块:

1
2
3
4
5
6
7
8
9
10
try {
    //Code
    System.exit(0)
}
catch (Exception ex) {
    //Log the exception
}
finally {
    System.exit(1)
}

如果没有异常,将调用哪个system.exit()?如果exit是一个返回语句,那么行system.exit(1)将始终(?)被召唤。但是,我不确定exit的行为是否与return不同。

在极端情况下,代码很难(如果不是不可能的话)重现,所以我不能编写单元测试。今天晚些时候,如果我有几分钟的空闲时间,我会尝试运行一个实验,但我还是很好奇,也许有人知道答案,可以在之前提供答案,或者万一我不能运行一个实验。


不,System.exit(0)不返回,最后一个块不执行。

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

下面是第二个案例的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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);
    }
  }

}


包括catch在内的简单测试也表明,如果System.exit(0)不抛出安全异常,它将是最后执行的语句(catchfinally根本不执行)。

如果System.exit(0)确实抛出了安全异常,则执行catchfinally语句。如果catchfinally都包含system.exit()报表,则只执行这些system.exit()报表前面的报表。

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

更多详细信息(个人博客)。


其他的答案包括,如果System.exit在不抛出SecurityException的情况下退出JVM,那么catchfinally块是如何不运行的,但它们没有显示"资源尝试"块对资源的影响:它们是关闭的吗?

根据JLS第14.20.3.2节:

The effect of the translation is to put the resource specification"inside" the try statement. This allows a catch clause of an extended try-with-resources statement to catch an exception due to the automatic initialization or closing of any resource.

Furthermore, all resources will have been closed (or attempted to be closed) by the time the finally block is executed, in keeping with the intent of the finally keyword.

也就是说,在catchfinally块运行之前,资源将是closed。如果它们是closed,即使catchfinally不运行,又会怎样?

下面是一些代码来证明"Try with Resources"语句中的资源也没有关闭。

我使用BufferedReader的一个简单子类,它在调用super.close之前打印一个语句。

1
2
3
4
5
6
7
8
9
10
11
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的测试用例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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!");
    }
}

输出:

In try

因此,不仅catchfinally块没有运行,如果System.exit成功,"try with resources"语句也不会有机会获得close的资源。


无论发生什么,最终块都将被执行…即使try块抛出任何可丢弃的(异常或错误)……

唯一不执行的case finally块是调用System.exit()方法..

1
2
3
4
5
6
7
8
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()语句之后。


如果您认为这种行为有问题,并且需要对您的System.exit调用进行精细控制,那么您唯一能做的就是将system.exit功能包装在自己的逻辑中。如果这样做,我们就可以最终执行块,并将资源作为退出流的一部分关闭。

我正在考虑用自己的静态方法包装System.exit调用和功能。在执行exit时,我将抛出ThrowableError的自定义子类,并使用Thread.setDefaultUncaughtExceptionHandler实现自定义未捕获异常处理程序来处理该异常。因此,我的代码变成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//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;
  }
}

这个策略还有一个额外的"好处",就是使这个逻辑可测试:要测试包含"主流"的方法实际上请求系统退出,我们所要做的就是捕获一个可丢弃的,断言为写类型。例如,我们的业务逻辑包装器的测试可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//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*/ }的任何地方都必须进行更新。对于具有自定义执行上下文的库,需要对它们进行改装以了解此异常。

总的来说,这对我来说似乎是一个很好的策略。这里还有人这么认为吗?


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

  • 如果System.exix(0)是try块的最后一行,这里有两个场景

    • 当出现异常时,最后执行块
    • 如果不存在异常,则不执行finally块
  • .

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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