首先,一个简单的测试代码:
package javaapplication23;
import java.io.IOException;
import java.util.logging.FileHandler;
public class JavaApplication23 {
public static void main(String[] args) throws IOException {
new FileHandler("./test_%u_%g.log", 10000, 100, true);
}
}
这个测试代码用Java 7只创建了一个文件"test_0_0.log",不管我运行这个程序的频率有多高。这是预期的行为,因为构造函数中的append参数被设置为true。
但是如果我在Java 8中运行这个示例,每次运行都会创建一个新文件(test_0_1.log、test_0_1.log、test_0_2.log,…)。我想这是一个bug。
Imho, Java中相关的变化是这样的:
@@ -413,18 +428,18 @@
// object. Try again.
continue;
}
- FileChannel fc;
+
try {
- lockStream = new FileOutputStream(lockFileName);
- fc = lockStream.getChannel();
- } catch (IOException ix) {
- // We got an IOException while trying to open the file.
- // Try the next file.
+ lockFileChannel = FileChannel.open(Paths.get(lockFileName),
+ CREATE_NEW, WRITE);
+ } catch (FileAlreadyExistsException ix) {
+ // try the next lock file name in the sequence
continue;
}
+
boolean available;
try {
- available = fc.tryLock() != null;
+ available = lockFileChannel.tryLock() != null;
// We got the lock OK.
} catch (IOException ix) {
// We got an IOException while trying to get the lock.
@@ -440,7 +455,7 @@
}
// We failed to get the lock. Try next file.
- fc.close();
+ lockFileChannel.close();
}
}
(完整:OpenJDK变更集6123:ac22a52a732c)
我知道FileHandler通常会被日志管理器关闭,但如果系统或应用程序崩溃或进程被杀死,情况并非如此。这就是为什么我在上面的示例代码中没有一个"close"语句。
现在我有两个问题:
你的意见是什么?这是臭虫吗?(在下面的评论和回答中几乎回答了)
2)你知道如何在Java 8中获得旧的Java 7行为吗?(更重要的问题是…)
谢谢你的回答
关闭FileHandler将删除'lck'文件。如果锁文件在小于update40 (java.util.logging)的JDK8版本下存在,那么FileHandler将会旋转。从OpenJDK的讨论来看,如果lock文件存在,以及当前进程无法锁定它,则决定始终旋转。给出的原因是,当锁文件存在时,旋转总是更安全。因此,如果您使用的是混合JDK版本的旋转模式,这将变得非常糟糕,因为JDK7版本将重用锁,但JDK8版本将离开它并旋转。这就是你对测试用例所做的。
使用JDK8,如果我从工作目录中清除所有日志和lck文件,然后运行:
public static void main(String[] args) throws IOException {
System.out.println(System.getProperty("java.runtime.version"));
new FileHandler("./test_%u.log", 10000, 100, true).close();
}
我总是看到一个名为'test_0.log.0'的文件。我使用JDK7得到相同的结果。
底线是,你必须确保你的FileHandlers是关闭的。如果它从未被垃圾收集或从日志树中删除,那么LogManager将关闭您的FileHandler。否则你必须关闭它。修复后,在运行新的补丁代码之前清除所有锁文件。然后要注意,如果JVM进程崩溃或终止,锁文件将不会被删除。如果您有一个I/O错误关闭您的锁文件将不会被删除。当下一个进程启动时,FileHandler将旋转。
正如您所指出的,如果以上条件发生超过100次运行,就有可能耗尽JDK8上的所有锁文件。一个简单的测试是在不删除日志和lck文件的情况下运行以下代码两次:
public static void main(String[] args) throws Exception {
System.out.println(System.getProperty("java.runtime.version"));
ReferenceQueue<FileHandler> q = new ReferenceQueue<>();
for (int i=0; i<100; i++) {
WeakReference<FileHandler> h = new WeakReference<>(
new FileHandler("./test_%u.log", 10000, 2, true), q);
while (q.poll() != h) {
System.runFinalization();
System.gc();
System.runFinalization();
Thread.yield();
}
}
}
然而,如果JDK-6774110被正确修复,上面的测试用例将无法工作。这个问题可以在OpenJDK站点的RFR下跟踪:8048020 - java.util.logging.FileHandler和FileHandler的回归。