11

首先,一个简单的测试代码:

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_0.log、test_0_1.log、test_0_2.log、...)。我认为这是一个错误。

恕我直言,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 会被 Logmanager 关闭,但如果系统或应用程序崩溃或进程被终止,情况并非如此。这就是为什么我在上面的示例代码中没有“关闭”语句的原因。

现在我有两个问题:

1)你的意见是什么?这是一个错误吗?(几乎在以下评论和答案中回答)

2) 您知道在 Java 8 中获取旧 Java 7 行为的解决方法吗?(更重要的问题……)

感谢您的回答。

4

1 回答 1

10

关闭 FileHandler 会删除“lck”文件。如果锁文件在低于 update 40 (java.util.logging) 的 JDK8 版本下完全存在,则 FileHandler 将旋转。从OpenJDK讨论中,如果 lck 文件存在,以及当前进程无法锁定它,则决定始终轮换。给出的原因是当锁定文件存在时旋转总是更安全。因此,如果您在混合使用 JDK 版本的情况下使用旋转模式,这会变得非常糟糕,因为 JDK7 版本将重用锁,但 JDK8 版本将保留它并旋转。这就是您对测试用例所做的事情。

如果我从工作目录中清除所有日志和 lck 文件,则使用 JDK8 ,然后运行:

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.FileHandlerFileHandler webrev的回归。

于 2014-06-20T12:56:49.637 回答