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



这个问题与finalize方法中的Exception和类似问题相反

我正在创建一个AutoCloseable类,如果没有正确关闭,它会带来严重的风险。我希望在这样的情况下很难失败,这样用户就不会意外忘记这样做

我意识到并同意,一般的最佳实践是Closeable优雅地失败,并尽最大努力减轻调用者的错误,但在这种情况下,调用者不想错过这一点。如果您在概念上不同意这个想法,我将感谢您的反馈,但在这种情况下,请将这个问题视为关于Java内部的学术练习。

我设想的是,如果调用了类的finalize()方法,并且实例尚未清理,则引发一个IllegalStateException并中断用户。然而,finalize()显式地吞噬了未捕获的异常,这使得这变得棘手。用户将从finalize()方法中看到的导致RuntimeException的最佳方法是什么?

这是一个演示类,我已经有了迄今为止:

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给我们机会,我仍然希望可能发生。

您不能强制从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);
    }
  }
}

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

不要在finalize中抛出异常。

那么,你如何才能沟通严重的编程错误的存在呢?你可以把它记录下来。但只有当有人读到日志时,这才有用。

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

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

您可以将一些消息发送到其他地方(例如,通过http或JMS(,但这需要其他地方进行侦听,并且比日志更不可忽略。

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

最新更新