Java 8中的java.util.logging.FileHandler出现故障了吗?

12

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

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”的文件,无论我运行程序多少次都是这样。这是期望的行为,因为构造函数中的附加参数设置为true。

但是,如果我在Java 8中运行此示例,则每次运行都会创建一个新文件(test_0_0.log,test_0_1.log,test_0_2.log等)。我认为这是一个bug。

我认为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关闭。但是,如果系统或应用程序崩溃或进程被终止,则不会这样。这就是为什么上面的示例代码中没有“close”语句的原因。

现在我有两个问题:

1)你怎么看?这是一个bug吗?(几乎在以下评论和答案中回答了)

2)你知道如何在Java 8中获得旧的Java 7行为吗?(更重要的问题...)

感谢您的回答。


1
你是否在FileHandler上调用close()方法? - laune
@laune:嗨,这对于这个例子并不重要。如果我不调用“close”,那么“.lck”文件将在应用程序停止后留下,但这不是重点,因为当应用程序或整个系统崩溃时,这也可能发生。在Java 7中,Filehandler检测到存在的lck文件,并检查是否可以锁定它。如果不能,则该文件正在被另一个进程使用,并且%u计数器会增加,但如果可以,则将此文件视为以前未运行的进程留下的文件,并使用现有的锁定文件。 - Remus
@laune:但在Java 8中,锁定文件的存在就会导致%u计数器递增。正如先前所述,这(lck文件的存在)也可能是由系统崩溃引起的。 - Remus
好的,好的:看起来似乎有一个错误。 - laune
你能试着找出为什么这个被改变了吗?我预计在从Java 7到Java 8的过程中,某些问题要求进行此更改。 - skiwi
2
@skiwi:这是程序员讨论的链接:http://mail.openjdk.java.net/pipermail/core-libs-dev/2012-November/012229.html - Remus
1个回答

10
关闭FileHandler会删除“lck”文件。如果在小于更新40的JDK8版本(java.util.logging)下锁定文件存在,则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将进行旋转。
正如您指出的那样,在JDK8上如果以上条件在100次运行中发生可能会使用完所有锁定文件。对此的简单测试是两次运行以下代码而不删除日志和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 - Regression on java.util.logging.FileHandlerFileHandler webrev进行跟踪。

当然,如果您关闭FileHandler,则lck文件将被删除,一切都很好。但是有许多原因导致“close”语句永远不会被调用:系统或应用程序崩溃,进程被杀死等等。这些文件将永远存在(除非您手动清理它们)。更糟糕的是:在存在100个lck文件之后,您无法再启动应用程序,因为硬编码了100个lck文件的最大值,如果达到此限制,将抛出未经检查的异常。 - Remus
重点是:我看不出来,为什么新的行为应该比旧的更好。在我使用Java 7进行测试时,来自其他JVM /进程的锁定被正确识别。而实际的行为就像一颗定时炸弹。 - Remus
FileHandler不会进行轮换(%g),它只是将%u递增到100,之后就会失败。 - Remus
1
@Remus,这种行为变化也让我不满意。我同意你的看法,耗尽所有锁文件可能会成为一个问题。我会在OpenJDK网站上提出这个问题。 - jmehrens
谢谢。如果您能在OpenJDK上开启的问题中发布一个链接,那就太好了。 - Remus
@Remus 添加了问题链接并提出了补丁建议。 - jmehrens

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