在finalize方法中触发用户可见的异常

时间:2014-05-20 14:10:27

标签: java finalizer autocloseable

这个问题与Exception in finalize method和类似问题相反。

我正在创建一个AutoCloseable课程,如果没有正确关闭,会带来严重的风险。我希望在这种情况下能够做到失败,这样用户就不会忘记这样做。

我意识到并同意,一般的最佳做法是让Closeable优雅地失败并尽力减轻调用者的错误,但在这种情况下,调用者不会想错过这个。如果您在概念上不同意这个想法,我将非常感谢您的反馈,但在这种情况下,请将该问题视为关于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()

注意:我也意识到{{1}}不能保证运行,所以我在这个方法周围实现的任何事情都不会发生。如果GC给我们机会的话,我仍然喜欢可能发生。

3 个答案:

答案 0 :(得分:1)

一种选择就是直接终止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)

答案 1 :(得分:1)

您不能强制从finalize方法抛出异常,因为此方法由任意依赖于实现的Thread执行,并且不清楚异常应该引发Thread

即使你知道要针对哪个线程,也有一个很好的理由为什么Thread.stop(Throwable)被弃用(并且自Java 8以来不受支持):导致线程在任意代码位置抛出任意throwable可以造成很大的伤害。例如。错过线程即将进入的另一个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);
    }
  }
}

答案 2 :(得分:0)

不要在finalize中抛出异常。

那么你如何能够解决那个严重的编程错误呢?你可以记录它。但只有当有人读取该日志时,这才有用。

你可以翻转一些标志,使整个应用程序无法使用(或者至少是你的库) - 将该异常存储在静态字段中(原来为null),并且在某些操作中如果设置则抛出它。 为了让它能够在JVM关闭后继续存在,你可以将它写入文件(但有时候你不能)并在启动时加载它并重新启动它(直到删除这个文件并重新启动应用程序)。

你可以关闭JVM(正如dimo414在我之前建议的那样),但同一个应用服务器上的其他应用程序不会感谢你,这会阻止关闭其他资源。

您可以在其他地方发送一些消息(例如,通过http或JMS),但这需要其他地方进行监听,而不是日志。

您可以实现多个选项,如何处理它并允许用户选择。