55

This question already has an answer here:

I'm aware of headaches that involve returning in try/catch/finally blocks - cases where the return in the finally is always the return for the method, even if a return in a try or catch block should be the one executed.

However, does the same apply to System.exit()? For example, if I have a try block:

try {
    //Code
    System.exit(0)
}
catch (Exception ex) {
    //Log the exception
}
finally {
    System.exit(1)
}

If there are no exceptions, which System.exit() will be called? If the exit was a return statement, then the line System.exit(1) would always (?) be called. However, I'm not sure if exit behaves differently than return.

The code is in an extreme case that is very difficult, if not impossible, to reproduce, so I can't write a unit test. I'm going to try to run an experiment later today, if I get a few free minutes, but I'm curious anyway, and perhaps someone on SO knows the answer and can provide it before or in case I can't run an experiment.


  • How is this an extreme case that is difficult to test? - JRL
  • The code I provided isn't. However, it's about getting time to write the test (which I no longer have to do, thanks to erickson). - Thomas Owens
  • Answering the exact question you ask needs one extra line of code in order to test : throw new Exception("Error"); right before System.exit(0) then compile and see what status code your executable returns. - Ki Jéy

6 답변


70

No. System.exit(0) doesn't return, and the finally block is not executed.

System.exit(int) can throw a SecurityException. If that happens, the finally block will be executed. And since the same principal is calling the same method from the same code base, another SecurityException is likely to be thrown from the second call.


Here's an example of the second case:

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

}


  • It is also possible for a StackOverflowException to prevent System.exit from happening, if the stack frame calling System.exit is just before the stack limit. These kind of boundary conditions can be engineered by hackers sometimes, so if you have security invariants that depend on this, install a Thread.UncaughtExceptionHandler that exits as a defense in depth. - Mike Samuel
  • Just to add that I've frequently encountered cases where the same block of code that the OP provides in his question will result in the finally block being executed (if Ant is using as an application launch script relying on Ant's java task) or not being executed (if the application is invoked directly using java from the console, without Ant). I guess that based on this answer it would seem that when the application is invoked from within Ant's java task a SecurityException is thrown. - Marcus Junius Brutus
  • StackOverflowException does not exist (unless you define one). It is StackOverflowError. Important little difference. - Peter Verhas

7

Simple tests including catch too reveal that if system.exit(0) does not throw a security exception, it will be the last executed statement (catch and finally are not executed at all).

If system.exit(0) does throw a security exception, catch and finally statements are executed. If both catch and finally contain system.exit() statements, only statements preceding these system.exit() statements are executed.

In both cases decribed above, if the try code belongs to a method called by another method, the called method does not return.

More details here (personal blog).


  • Thank you for taking care of the attribution. :) - Andrew Barber

3

Other answers have covered how the catch and finally blocks don't run if System.exit exits the JVM without throwing a SecurityException, but they don't show what happens in a "try-with-resources" block to the resources: Are they closed?

According to the JLS, Section 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.

That is, resources will be closed before a catch or finally block runs. What if they are closed somehow even if catch and finally don't run?

Here's some code to demonstrate that the resources in a "try-with-resources" statement aren't closed either.

I use a simple subclass of BufferedReader that prints a statement before calling super.close.

class TestBufferedReader extends BufferedReader {
    public TestBufferedReader(Reader r) {
        super(r);
    }

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

Then I set up the test case of calling System.exit in the try-with-resources statement.

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

Output:

In try

Therefore, not only do catch and finally blocks not run, a "try-with-resources" statement won't get a chance to close its resources if System.exit succeeds.


3

finally block will be executed no matter what....even if try block throws any throwable(exception or error).....

only case finally block does not execute...is when we call System.exit() method..

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

It will not execute finally block. The program will be terminated after System.exit() statement.


1

If you consider this behaviour problematic, and you need fine control over your System.exit calls, then the only thing you can do is wrap the System.exit functionality in your own logic. If we do that, we can get finally blocks executed and get resources closed as part of our exit flow.

What I'm considering doing is wrapping the System.exit call & functionality in my own static method. In my implementation of exit I would throw a custom subclass of Throwable or Error, and implement a custom Uncaught exception handler with Thread.setDefaultUncaughtExceptionHandler to handle that exception. Thus my code becomes:

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

This strategy has the added "benefit" of making this logic testable: to test that the method containing our "main flow" actually requests the system to exit, all we have to do is catch a throwable and assert that is the write type. For example, a test for our business logic wrapper might look like:

//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)

The major downside to this strategy is that it is a way of getting functionality out of exception flow, which often has unintended consequences. The most obvious one, in this case, is that anywhere you've written try { ... } catch(Throwable ex){ /*doesnt rethrow*/ } will have to be updated. In the case of libraries that have custom execution contexts, they will need to be retrofitted to also understand this exception.

On balance, this seems like a good strategy to me. Does anybody else here think so?


0

  1. In example below, if System.exit(0) is before the exception line, the program will be terminated normally, so the FINALLY will not execute.

  2. If the System.exix(0) is the last line of the try block, here we have 2 scenarios

    • when exception is present then finally block is executed
    • when exception is not present then finally block is not executed

.

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

Linked


Related

Latest