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

Java 触发finalize方法中的用户可见异常,java,finalizer,autocloseable,Java,Finalizer,Autocloseable,这个问题与类似的问题相反 我正在创建一个可自动关闭的类,如果未正确关闭,将带来严重风险。在这种情况下,我希望努力失败,这样用户就不会意外地忘记这么做 我意识到并同意,一般的最佳实践是Closeables优雅地失败,并尽最大努力减轻调用者的错误,但在这种情况下,调用者不希望错过这一点。如果你在概念上不同意这个想法,我会感谢你的反馈,但是在这种情况下,考虑关于java内部构件的学术练习的问题。 我设想的是,如果调用了我的类的finalize()方法,并且实例尚未清理,则引发一个IllegalStat

这个问题与类似的问题相反

我正在创建一个
可自动关闭的
类,如果未正确关闭,将带来严重风险。在这种情况下,我希望努力失败,这样用户就不会意外地忘记这么做

我意识到并同意,一般的最佳实践是
Closeable
s优雅地失败,并尽最大努力减轻调用者的错误,但在这种情况下,调用者不希望错过这一点。如果你在概念上不同意这个想法,我会感谢你的反馈,但是在这种情况下,考虑关于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给我们机会,我仍然希望它可能发生。

一种选择是直接终止JVM:

@Override
protected void finalize() throws IllegalStateException {
  if(!safelyClosed) {
    leftUnclosed.printStackTrace(System.err);
    System.exit(255);
  }
}
以下内容非常一致地复制了所需的行为,包括显示未关闭的
可关闭的
创建位置的跟踪:

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在使用后未正确关闭
在SeriouslyCloseable.(SeriouslyCloseable.java:5)
方法(SeriouslyCloseable.java:23)
位于SeriouslyCloseable.main(SeriouslyCloseable.java:28)

不要在finalize中抛出异常

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

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

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

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


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

您不能强制从
finalize
方法引发异常,因为此方法由任意的、依赖于实现的
线程执行,并且不清楚异常应在哪个
线程中引发

即使您知道要针对哪个线程,也有一个很好的理由说明
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);
    }
  }
}
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);
    }
  }
}