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」ファイルを 1 つだけ作成します。コンストラクターの追加パラメーターが 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 によって閉じられることはわかっていますが、システムまたはアプリケーションがクラッシュしたり、プロセスが強制終了されたりした場合は、そうではありません。これが、上記のサンプル コードに "close" ステートメントがない理由です。

今、私は2つの質問があります:

1) あなたの意見は? これはバグですか?(以下のコメントと回答でほぼ答えられています)

2) Java 8 で古い Java 7 の動作を取得するための回避策を知っていますか? (もっと重要な質問...)

回答ありがとうございます。

4

1 に答える 1

10

FileHandler を閉じると、「lck」ファイルが削除されます。update 40 (java.util.logging) より前のバージョンの JDK8 でロック ファイルが存在する場合、FileHandler はローテーションします。OpenJDKの議論から、現在のプロセスがロックできない場合に加えて、lck ファイルが存在する場合は常にローテーションするという決定が下されました。与えられた理由は、ロック ファイルが存在する場合は常にローテーションする方が安全だからです。JDK7バージョンはロックを再利用しますが、JDK8バージョンはロックを残してローテーションするため、JDKバージョンが混在するローテーションパターンを使用している場合、これは非常に厄介です。これは、テストケースで行っていることです。

作業ディレクトリからすべてのログ ファイルと 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 ファイルを削除せずに次のコードを 2 回実行します。

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.FileHandler and FileHandler webrevで追跡できます。

于 2014-06-20T12:56:49.637 に答える