Как Java System.exit () работает с блоками try / catch / finally?

Мне известны головные боли, связанные с возвратом в блоки try / catch / finally - случаи, когда возвращение в finally всегда является возвратом для метода, даже если возврат в блоке try или catch должен быть выполнен.

Однако применимо ли то же самое к System.exit ()? Например, если у меня есть блок попыток:

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

Если нет исключений, какой System.exit () будет вызываться? Если бы выход был оператором возврата, то всегда (?) Вызывалась бы строка System.exit (1). Однако я не уверен, что exit ведет себя иначе, чем return.

Код находится в крайнем случае, который очень сложно, если не невозможно, воспроизвести, поэтому я не могу написать модульный тест. Я собираюсь попробовать провести эксперимент позже сегодня, если у меня будет несколько свободных минут, но мне все равно любопытно, и, возможно, кто-то из SO знает ответ и может предоставить его раньше или в случае, если я не могу запустить эксперимент.


person Thomas Owens    schedule 11.09.2009    source источник
comment
Что это за крайний случай, который трудно проверить?   -  person JRL    schedule 11.09.2009
comment
Предоставленный мной код - нет. Однако пора написать тест (что мне больше не нужно, благодаря Эриксону).   -  person Thomas Owens    schedule 11.09.2009
comment
Чтобы ответить на точный вопрос, который вы задаете, нужна одна дополнительная строка кода для проверки: throw new Exception("Error"); прямо перед System.exit(0), затем скомпилируйте и посмотрите, какой код состояния возвращает ваш исполняемый файл.   -  person Ki Jéy    schedule 01.09.2017


Ответы (6)


Нет. 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);
    }
  }

}
person erickson    schedule 11.09.2009
comment
Также возможно для StackOverflowException предотвратить System.exit, если кадр стека, вызывающий System.exit, находится непосредственно перед пределом стека. Такие граничные условия иногда могут быть созданы хакерами, поэтому, если у вас есть инварианты безопасности, зависящие от этого, установите Thread.UncaughtExceptionHandler, который выходит в качестве глубокой защиты. - person Mike Samuel; 06.03.2011
comment
Просто добавлю, что я часто сталкивался со случаями, когда тот же блок кода, который OP предоставляет в своем вопросе, приводит к выполнению блока finally (если Ant используется в качестве сценария запуска приложения, полагающегося на задачу Ant java) или не выполняется. выполняется (если приложение вызывается напрямую с помощью java из консоли, без Ant). Я предполагаю, что на основе этого ответа может показаться, что когда приложение вызывается из java задачи Ant, выдается SecurityException. - person Marcus Junius Brutus; 13.09.2014
comment
StackOverflowException не существует (если вы его не определите). Это StackOverflowError. Важная небольшая разница. - person Peter Verhas; 31.05.2018
comment
Обработка System.exit в Java - это ошибка дизайна, ИМО. Python делает это лучше. - person Dávid Horváth; 21.07.2019
comment
Есть ли что-нибудь, что можно было бы использовать вместо System.exit (), что позволило бы выполнить блок finally? - person ed22; 27.08.2019
comment
@ ed22 Я полагаю, вы имеете в виду, что не хотите просто удалять вызов exit(), но у вас все еще есть код для безоговорочного выполнения перед выходом. В этом случае вы можете зарегистрировать ловушку выключения, содержащую код из блока finally. Однако хорошее решение будет сильно зависеть от рассматриваемого кода, поэтому лучше всего было бы опубликовать конкретный вопрос. - person erickson; 27.08.2019

Простые тесты, включая catch, также показывают, что если system.exit(0) не вызывает исключение безопасности, это будет последний выполненный оператор (catch и finally вообще не выполняются).

Если system.exit(0) вызывает исключение безопасности, выполняются операторы catch и finally. Если и catch, и finally содержат операторы system.exit(), выполняются только операторы, предшествующие этим операторам system.exit().

В обоих описанных выше случаях, если код try принадлежит методу, вызываемому другим методом, вызываемый метод не возвращается.

Подробнее здесь (личный блог).

person Jérôme Verstrynge    schedule 28.07.2012
comment
Спасибо, что позаботились об атрибуции. :) - person Andrew Barber; 30.07.2012

В других ответах рассказывается, как блоки catch и finally не запускаются, если System.exit выходит из JVM, не выбрасывая SecurityException, но они не показывают, что происходит в блоке try-with-resources с ресурсами: закрыты ли они?

Согласно JLS, раздел 14.20 .3.2:

В результате перевода спецификация ресурса помещается в оператор try. Это позволяет предложению catch расширенного оператора try-with-resources перехватывать исключение из-за автоматической инициализации или закрытия любого ресурса.

Кроме того, все ресурсы будут закрыты (или попытаться закрыть) к моменту выполнения блока finally в соответствии с намерением ключевого слова finally.

То есть ресурсы будут closed перед запуском блока catch или finally. Что, если они каким-то образом closed, даже если catch и finally не работают?

Вот код, демонстрирующий, что ресурсы в инструкции 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();
    }
}

Затем я настроил тестовый пример вызова System.exit в операторе try-with-resources.

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

Вывод:

В попытке

Следовательно, не только блоки catch и finally не выполняются, но и оператор try-with-resources не получит возможности close использовать свои ресурсы, если System.exit завершится успешно.

person rgettman    schedule 20.11.2015

блок finally будет выполнен независимо от того, что .... даже если блок try выдает какие-либо бросаемые (исключение или ошибку) .....

единственный случай, когда блок finally не выполняется ... это когда мы вызываем метод 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 ().

person Chinmoy    schedule 06.07.2014

Если вы считаете такое поведение проблематичным и вам нужен точный контроль над вашими System.exit вызовами, то единственное, что вы можете сделать, - это обернуть функциональность System.exit в свою собственную логику. Если мы это сделаем, мы сможем выполнить блоки finally и закрыть ресурсы как часть нашего потока выхода.

Я собираюсь обернуть System.exit вызов и функциональность в мой собственный статический метод. В моей реализации exit я бы выбрал настраиваемый подкласс Throwable или Error и реализовал бы настраиваемый обработчик исключений Uncaught с 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*/ }, придется обновить. В случае библиотек, которые имеют настраиваемые контексты выполнения, их необходимо будет модернизировать, чтобы также понимать это исключение.

В целом, мне кажется, что это хорошая стратегия. Кто-нибудь еще так думает?

person Groostav    schedule 19.09.2016

  1. В примере ниже, если System.exit(0) находится перед строкой исключения, программа будет завершена нормально, поэтому НАКОНЕЦ не будет выполняться.

  2. Если System.exix(0) - последняя строка блока try, здесь у нас есть 2 сценария

    • when exception is present then finally block is executed
    • когда исключение отсутствует, блок 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
person honey    schedule 15.08.2015