Java:在finalize方法中写文件

3
在我的理解中,单例对象只有在应用程序即将终止时才会被销毁。因此,在C++中,我编写了一个Singleton类来记录我的应用程序,在该Singleton记录器的析构函数中,我记录了应用程序终止的时间。在C++中一切都很完美。
现在我想在Java中拥有相同的记录器,但是在Java中没有析构函数,所以我为该单例记录器实现了finalize方法。但似乎finalize方法从未被调用。 因此,我在代码中添加了那个System.runFinalizersOnExit(true);行(尽管我知道它已被弃用),并且每次在应用程序终止之前都会调用finalize方法。但仍然存在问题!如果我尝试在finalize方法中写入任何文件,则无法正常工作,尽管System.out可以正常工作! :(
你们能帮我解决这个问题吗?这是我要做的示例代码:
单例记录器类:
public class MyLogger {
    FileWriter writer;
    private MyLogger() {
        try {
            this.writer = new FileWriter("log.txt");
        }
        catch (IOException ex) {
        }
    }

    public static MyLogger getInstance() {
        return MyLoggerHolder.INSTANCE;
    }

    private static class MyLoggerHolder {
        private static final MyLogger INSTANCE = new MyLogger();
    }

    @Override
    protected void finalize () {
        try {
            super.finalize();
            System.out.println("Here"); //worked correctly.
            this.writer.write(new Date().toString()+System.getProperty("line.separator"));
            this.writer.write("End");
            this.writer.flush(); //does not work!
            this.writer.close();
        }
        catch (Throwable ex) {
        }
    }
    public synchronized void log(String str) {
        try {
            this.writer.write(new Date().toString()+System.getProperty("line.separator"));
            this.writer.write(str+"\n");
            this.writer.flush();
        }
        catch (IOException ex) {
        }
    }
 }

主要:

public class Main {
    public static void main(String[] args) {
        System.runFinalizersOnExit(true);
        MyLogger logger = MyLogger.getInstance();
        logger.log("test");
    }
}
6个回答

3
这是一个奇特的问题。注意:我不会编写自己的日志记录类 - 可以看一下log4j之类的... 正如已经说明的那样,我真的不会使用finalize。

...

根据您的说法,您的目的只是在程序停止时运行某些内容。我建议注册一个 shutdownhook(关闭钩子)而不是...。shutdownhook 是一个用户定义的对象,它继承 Thread 类。例如,您可以在记录器内定义一个静态内部类。以下是一个示例:

http://www.crazysquirrel.com/computing/java/basics/java-shutdown-hooks.jspx

以下是API文档: http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Runtime.html#addShutdownHook%28java.lang.Thread%29


我刚刚完成了它,没有使用任何finalize! 有人已经说过了,但你提供了链接,这更有帮助。 感谢你和每个人 :) - sowrov

2

Effective Java 2nd Edition: 第7条: 避免使用finalizers

finalizers是不可预测的,常常是危险的,而且通常是不必要的。它们的使用可能导致不稳定的行为、性能下降和可移植性问题。Finalizers只有少数有效的用途[...],但一般来说,你应该避免使用finalizers。

[...] 唯一声称保证终结的方法是System.runFinalizersOnExit及其邪恶的孪生兄弟Runtime.runFinalizersOnExit。这些方法存在致命缺陷,已被弃用。


2
如其他的答案所说,使用finalize被认为是不良实践。当虚拟机关闭时,官方建议要做某件事情的方法是使用“shutdown hook(关闭挂钩)”。
关闭挂钩是一个您创建但不启动的线程对象。您使用Runtime.getRuntime().addShutdownHook(Thread)注册它,当虚拟机关闭时它将被调用。

+1 - 请注意,JVM 可能会以不运行关闭挂钩的方式终止。例如,JVM 崩溃或 "kill -9" 命令、硬件故障、停电或陨石撞击都可能导致这种情况发生。 - Stephen C

1

Josh Bloch在他的书《Effective Java》中写道,在finalizers中做任何事情都是一种不好的实践。

您的选择是在关闭应用程序时手动执行此操作:

  • 如果您使用System.exit(0)关闭它,请在那里调用该代码
  • 如果用户关闭它,请添加一个监听器并在那里调用它。

0
除了使用 finalize 不安全之外(我相信你已经看到了,因为你正在使用的 System.runFinalizersOnExit 方法已被弃用),你的 finalize 方法可能会抛出异常/错误。在这种情况下,你的异常/错误将在 catch 语句中被捕获,但没有任何处理。更好的做法是:
try{
   System.out.println("Here"); //worked correctly.
   this.writer.write(new Date().toString()+System.getProperty("line.separator"));
   this.writer.write("End");
   this.writer.flush(); //does not work!
   this.writer.close();
}
catch(Throwable e){
   //Log or handle the issue
}
finally{
   super.finalize();
}

尽管如此,通常情况下在Java中使用finalize并不是一个好的实践,应该避免使用。


0

我建议,与其尝试自己编写日志框架,不如查看现有的 Java 日志框架之一。我使用 log4j,但有很多选择!您将获得更稳定的代码、更少的调试工作和可能更快的性能。而且,它们不需要复杂的 finalizer 行为。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接