Java 如何确保在销毁对象之前调用方法?

Java 如何确保在销毁对象之前调用方法?,java,garbage-collection,Java,Garbage Collection,我编写了一个自定义日志类,其行为与诸如System.out或System.err之类的PrintWriter非常相似。主要的区别是当myLogger.printf(“helloworld!\n”)时,数据不会直接写入日志文件,而是写入内部队列,该队列只会通过flush()刷新到输出文件方法。因此,代码的使用如下所示: myLogger.println("Line 1."); myLogger.println("Line 3."); myLogger.println("Actually that

我编写了一个自定义日志类,其行为与诸如System.out或System.err之类的PrintWriter非常相似。主要的区别是当
myLogger.printf(“helloworld!\n”)时,数据不会直接写入日志文件,而是写入内部队列,该队列只会通过
flush()刷新到输出文件方法。因此,代码的使用如下所示:

myLogger.println("Line 1.");
myLogger.println("Line 3.");
myLogger.println("Actually that was Line 2. THIS is Line 3!");
myLogger.flush();
2016-03-30 15:44:45::389> Line 1.
2016-03-30 15:44:45::390> Line 3.
2016-03-30 15:44:45::395> Actually that was Line 2. THIS is Line 3!
@Override protected void finalize() throws Throwable {
    // TODO Auto-generated method stub
    // do some crazy stuff here
    super.finalize();
}
它应该给出如下(某种程度上)的输出:

myLogger.println("Line 1.");
myLogger.println("Line 3.");
myLogger.println("Actually that was Line 2. THIS is Line 3!");
myLogger.flush();
2016-03-30 15:44:45::389> Line 1.
2016-03-30 15:44:45::390> Line 3.
2016-03-30 15:44:45::395> Actually that was Line 2. THIS is Line 3!
@Override protected void finalize() throws Throwable {
    // TODO Auto-generated method stub
    // do some crazy stuff here
    super.finalize();
}
然而,我的问题是当用户犯错误时。也就是说,他们忘记调用
flush()
,他们写入记录器的数据永远不会转储到文件中,程序关闭时也不会刷新数据

我不能在每次调用后都刷新,因为它首先会破坏编写这个类的目的。而让系统管理自动冲洗也同样会弄巧成拙

我的想法是在对象的
finalize()
方法中调用
flush()
,但正如我在本网站上读到的,不能保证会调用
finalize()

为了清楚起见,以下是
flush()
方法的外观:

public void flush() {
    open();
    while(!unwrittenLogs.isEmpty()) {
        String line = unwrittenLogs.poll();
        log.print(line);
    }
    close();
}

private void open() {
    if(log == null) {
        try {
            log = new PrintWriter(new FileOutputStream(logFile, true));
        } catch (FileNotFoundException e) {
            System.err.printf("Unable to open Log File.\n%s\n",e.getMessage());
            e.printStackTrace(System.err);
        }
    }
}

private void close() {
    if(log != null) {
        log.close();
        log = null;
    }
}

那么,我的最佳选择是什么,以确保在程序退出之前刷新记录器?

将您的方法放入finalize方法中,如下所示:

myLogger.println("Line 1.");
myLogger.println("Line 3.");
myLogger.println("Actually that was Line 2. THIS is Line 3!");
myLogger.flush();
2016-03-30 15:44:45::389> Line 1.
2016-03-30 15:44:45::390> Line 3.
2016-03-30 15:44:45::395> Actually that was Line 2. THIS is Line 3!
@Override protected void finalize() throws Throwable {
    // TODO Auto-generated method stub
    // do some crazy stuff here
    super.finalize();
}
这是一个对象销毁的示例

要在JVM关闭之前保存数据,请使用关闭挂钩:

public static void main(final String[] args) {
    Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override public void run() {
            // TODO Auto-generated method stub
            // do the other crazy stuff in here
            super.run();
        }
    });
}
但两者都不是100%安全的。 1) 您可以在不运行所有终结器的情况下关闭JVM
2) 如果通过任务管理器/kill信号终止JVM进程,将不会触发关闭挂钩

将您的方法放入finalize方法,如下所示:

myLogger.println("Line 1.");
myLogger.println("Line 3.");
myLogger.println("Actually that was Line 2. THIS is Line 3!");
myLogger.flush();
2016-03-30 15:44:45::389> Line 1.
2016-03-30 15:44:45::390> Line 3.
2016-03-30 15:44:45::395> Actually that was Line 2. THIS is Line 3!
@Override protected void finalize() throws Throwable {
    // TODO Auto-generated method stub
    // do some crazy stuff here
    super.finalize();
}
这是一个对象销毁的示例

要在JVM关闭之前保存数据,请使用关闭挂钩:

public static void main(final String[] args) {
    Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override public void run() {
            // TODO Auto-generated method stub
            // do the other crazy stuff in here
            super.run();
        }
    });
}
但两者都不是100%安全的。 1) 您可以在不运行所有终结器的情况下关闭JVM
2) 如果通过任务管理器/kill信号终止JVM进程,则不会触发关闭挂钩

您提出了两个不同的问题:如何确保在收集对象之前调用记录器的
flush()
方法,以及如何确保在程序退出之前调用它。正如您从研究中收集到的,记录器可能在程序退出之前未被收集,因此终结器无法保证方法将被调用


如果您对记录器在VM关闭之前不符合GC条件感到满意,那么您可以向运行时注册一个日志,这将刷新记录器。这样的钩子需要保留对记录器的引用,运行时将保留对钩子的引用(一个未启动的
线程
),直到它关闭,因此记录器将保持不符合GC的资格,直到运行时执行其关闭钩子。

您已经问了两个不同的问题:如何确保记录器的
刷新()
方法是在收集该对象之前调用的,以及如何确保在程序退出之前调用该方法。当您从研究中收集时,记录器可能不会在程序退出之前被收集,因此终结器无法保证将调用该方法


如果您对记录器在VM关闭之前不符合GC条件感到满意,那么您可以向运行时注册一个用于刷新记录器的日志。这样的挂钩需要保留对记录器的引用,而运行时将保留对挂钩的引用(未启动的
线程
)因此,在运行时执行其关闭挂钩之前,记录器将保持不符合GC的资格。

可刷新写入程序的另一种方法:

  • 使用
    TransferQueue
    实现
  • 创建一个单独的日志编写器线程
  • 对队列执行
    take()
    。[阻塞]
  • 打开日志文件,此时可以根据需要进行截断和日志旋转
  • 写入记录的项目,使用
    poll()
    [非阻塞]
  • 刷新并关闭日志文件
  • 如果(应用程序仍在运行),则转到3
  • 这种方法有几个优点:

    • 日志线程将承受IO和刷新成本,而不是执行业务逻辑的线程
    • 它是线程安全的,因为只有一个写线程
    • 只要TransferQueue实现是无锁的,日志项提交就是无锁的,例如
      LinkedTransferQueue
    • 日志记录器线程将使VM保持活动状态,直到写入日志,假设
      thread.setDaemon(false)
      ,这是默认设置

    一种可供选择的写作方法:

  • 使用
    TransferQueue
    实现
  • 创建一个单独的日志编写器线程
  • 对队列执行
    take()
    。[阻塞]
  • 打开日志文件,此时可以根据需要进行截断和日志旋转
  • 写入记录的项目,使用
    poll()
    [非阻塞]
  • 刷新并关闭日志文件
  • 如果(应用程序仍在运行),则转到3
  • 这种方法有几个优点:

    • 日志线程将承受IO和刷新成本,而不是执行业务逻辑的线程
    • 它是线程安全的,因为只有一个写线程
    • 只要TransferQueue实现是无锁的,日志项提交就是无锁的,例如
      LinkedTransferQueue
    • 日志记录器线程将使VM保持活动状态,直到写入日志,假设
      thread.setDaemon(false)
      ,这是默认设置

    您可以做两件事:在每次X写入后刷新,其中X是可配置的。您还可以最后一次调用
    flush()
    。关闭挂钩也不会总是被调用,因为VM