Java 如何确保在销毁对象之前调用方法?
我编写了一个自定义日志类,其行为与诸如System.out或System.err之类的PrintWriter非常相似。主要的区别是当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
myLogger.printf(“helloworld!\n”)调用code>时,数据不会直接写入日志文件,而是写入内部队列,该队列只会通过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