Запуск видимого пользователем исключения в методе finalize

Этот вопрос противоположен Exception in finalize method и подобным вопросам.

Я создаю класс AutoCloseable, который представляет серьезные риски, если его не закрыть должным образом. В таком случае я ищу отказоустойчивость, чтобы пользователи случайно не забыли это сделать.

Я понимаю и согласен с тем, что общая лучшая практика заключается в том, чтобы Closeables изящно терпели неудачу и делали все возможное, чтобы смягчить ошибку вызывающего, но в этом случае вызывающие не захотят пропустить это. Если вы не согласны с этой идеей концептуально, я буду признателен за ваши отзывы, но в этом случае считайте вопрос академическим упражнением о внутреннем устройстве Java.

То, что я предполагаю, вызывает IllegalStateException и прерывает пользователя, если вызывается метод finalize() моего класса, а экземпляр еще не очищен. Однако finalize() явно проглатывает неперехваченные исключения, что усложняет задачу. Каков наилучший способ вызвать RuntimeException, который пользователь увидит из метода finalize()?

Вот демонстрационный класс того, что у меня есть:

public class SeriouslyCloseable implements AutoCloseable {
  // We construct an Exception when the class is initialized, so that the stack
  // trace informs where the class was created, rather than where it is finalized
  private final IllegalStateException leftUnclosed = new IllegalStateException(
      "SEVERE: "+getClass().getName()+" was not properly closed after use");
  private boolean safelyClosed = false;

  @Override
  public void close() {
    // do work
    safelyClosed = true;
  }

  @Override
  protected void finalize() throws IllegalStateException {
    if(!safelyClosed) {
      // This is suppressed by the GC
      throw leftUnclosed;
    }
  }
}

Примечание. Я также понимаю, что запуск finalize() не гарантируется, поэтому все, что я реализую вокруг этого метода, не обязательно произойдет. Я все еще хотел бы, чтобы это вероятно произошло, если GC даст нам шанс.


person dimo414    schedule 20.05.2014    source источник


Ответы (3)


Вы не можете принудительно вызвать исключение из метода finalize, так как этот метод выполняется произвольным, зависящим от реализации Thread, и неясно, в каком Thread должно возникнуть исключение.

Даже если у вас было представление о том, к какому потоку стремиться, есть веская причина, по которой Thread.stop(Throwable) устарела (и не поддерживается, начиная с Java 8): создание потоком произвольного броска в произвольном месте кода может нанести большой вред. Например. пропустить еще одну close() операцию, которую поток собирался ввести. Кроме того, поток, совершивший ошибку, может уже не существовать во время вызова вашего метода finalize.


В конце концов, речь идет не о создании, а об сообщении об исключении, которого вы хотите добиться. Вы можете имитировать исходное поведение без подавления следующим образом:

@Override
protected void finalize() throws Throwable {
    if(!safelyClosed) {
        final Thread t = Thread.currentThread();
        t.getUncaughtExceptionHandler().uncaughtException(t, leftUnclosed);
    }
}

По умолчанию он выводит трассировку стека исключений на консоль. Преимущество по сравнению с вызовом printStackTrace вручную заключается в том, что он работает вместе с возможно установленным обработчиком исключений для конкретного приложения:

import java.util.logging.Level;
import java.util.logging.Logger;

public class ThrowableInFinalize {
  public static void main(String[] args) throws InterruptedException {
    Thread.setDefaultUncaughtExceptionHandler(
                                          new Thread.UncaughtExceptionHandler() {
      public void uncaughtException(Thread t, Throwable e) {
        Logger.getLogger("ThrowableInFinalize")
              .log(Level.SEVERE, "uncaught exception", e);
      }
    });
    new ThrowableInFinalize();
    System.gc();
    Thread.sleep(1000);
  }

  private final IllegalStateException leftUnclosed = new IllegalStateException(
      "SEVERE: "+getClass().getName()+" was not properly closed after use");
  private boolean safelyClosed;
  @Override
  protected void finalize() throws Throwable {
    if(!safelyClosed) {
      final Thread t = Thread.currentThread();
      t.getUncaughtExceptionHandler().uncaughtException(t, leftUnclosed);
    }
  }
}
person Holger    schedule 21.05.2014

Одним из вариантов было бы просто полностью завершить работу JVM:

@Override
protected void finalize() throws IllegalStateException {
  if(!safelyClosed) {
    leftUnclosed.printStackTrace(System.err);
    System.exit(255);
  }
}

Следующее довольно последовательно воспроизводило желаемое поведение, включая отображение трассировки того, где был создан незакрытый Closeable:

private static void resourceLeak() {
  SeriouslyCloseable sc = new SeriouslyCloseable();
  //sc.close();
}

public static void main(String[] args) throws InterruptedException {
  resourceLeak();
  System.gc();
  Thread.sleep(1000);
  System.out.println("Exiting Normally");
}
java.lang.IllegalStateException: SEVERE: SeriouslyCloseable was not properly closed after use
        at SeriouslyCloseable.<init>(SeriouslyCloseable.java:5)
        at SeriouslyCloseable.method(SeriouslyCloseable.java:23)
        at SeriouslyCloseable.main(SeriouslyCloseable.java:28)
person dimo414    schedule 20.05.2014

Не бросайте исключение в finalize.

Итак, как вы можете сообщить, что присутствует серьезная ошибка программирования? Вы могли бы зарегистрировать это. Но это полезно только тогда, когда кто-то будет читать этот журнал.

Вы можете перевернуть какой-нибудь флаг, который сделает непригодным для использования все приложение (или, по крайней мере, вашу библиотеку) - сохраните это исключение в статическом поле (изначально нулевое) и в какой-то операции выкиньте его, если оно установлено. Чтобы он пережил выключение JVM, вы можете записать его в файл (но иногда вы не можете) и загрузить его при запуске и возобновить его (пока такой файл не будет удален и приложение не перезапущено).

Вы можете отключить JVM (как предложил dimo414 до меня), но другие приложения на том же сервере приложений не будут вам благодарны, и это предотвратит закрытие других ресурсов.

Вы можете отправить какое-то сообщение куда-то еще (например, через http или JMS), но для этого потребуется, чтобы кто-то еще прослушивал и менее игнорировался, чем журналы.

И вы можете реализовать несколько вариантов того, как с этим справиться, и позволить пользователю выбирать.

person Alpedar    schedule 20.05.2014