1

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

我正在创建一个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 给我们机会,我仍然希望它可能发生。

4

3 回答 3

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)
于 2014-05-20T14:26:12.543 回答
1

您不能强制从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);
    }
  }
}
于 2014-05-21T10:07:05.477 回答
0

不要在 finalize 中抛出异常。

那么,如何传达存在严重的编程错误呢?你可以记录下来。但这仅在有人阅读该日志时才有用。

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

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

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

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

于 2014-05-20T14:38:55.093 回答