2017-10-18 14 views
0

私のアプリに奇妙なバグがありました。私はすでに回避策で解決しましたが、なぜこのバグが起きているのか不思議です。Files.walkFileTreeがカスタムFileVisitorでディレクトリ記述子を漏らしている

ここでは、空のディレクトリを削除するカスタムFileVisitorの例を示します。ディレクトリが空ではなく、まだそれらのディレクトリを移動すると、ディレクトリ記述子がリークします。アプリのPIDにlsofを使用すると、いくつかの同じディレクトリを参照する一連のディスクリプタが表示されます。

private String getOldestFile() { 
    fileVisitor.clearOldestFile(); 

    try { 
     // FIXME: this was throwing FileSystemException: Too many open files after some time running. Leaking file descriptors!! 
     Files.walkFileTree(Paths.get(csvPath), fileVisitor); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

    return fileVisitor.getOldestFile().toString(); 
} 

class CustomFileVisitor extends SimpleFileVisitor<Path> { 
    private Path oldestFile = null; 

    Path getOldestFile() { 
     return oldestFile; 
    } 

    void clearOldestFile() { 
     oldestFile = null; 
    } 

    @Override 
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 
     if (attrs.isDirectory()) 
      return FileVisitResult.CONTINUE; 

     if (oldestFile == null) 
      oldestFile = file; 

     if (oldestFile.compareTo(file) > 0) 
      oldestFile = file; 

     return FileVisitResult.CONTINUE; 
    } 

    @Override 
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 
     if (dir.equals(Paths.get(csvPath))) 
      return FileVisitResult.CONTINUE; 

     if (Files.list(dir).collect(Collectors.toList()).size() == 0) 
      Files.delete(dir); // throws an exception if folder is not empty -> mustn't delete folder with files 

     return FileVisitResult.CONTINUE; 
    } 
} 

CustomFileVisitorは外側のクラスで、一度だけ作成され、機能がfilename = getOldestFile();

EDITのように定期的に呼び出されます。lsof -p {PID}出力を投稿します。最初はthisのようなPIDを見つけました。

出力はこのようになりますが、その数千の行だけが出力されます。 "/ home/leon/Development/data /"はFiles.walkFileTreeへの入力です。

java 14965 leon 285r  DIR    8,2  4096 1970798 /home/leon/Development/data/2017 
java 14965 leon 286r  DIR    8,2  4096 1970799 /home/leon/Development/data/2017/10 
java 14965 leon 287r  DIR    8,2  4096 1970799 /home/leon/Development/data/2017/10 
java 14965 leon 288r  DIR    8,2 36864 1970800 /home/leon/Development/data/2017/10/17 
java 14965 leon 289r  DIR    8,2 36864 1970800 /home/leon/Development/data/2017/10/17 
java 14965 leon 290r  DIR    8,2  4096 1970798 /home/leon/Development/data/2017 
java 14965 leon 291r  DIR    8,2  4096 1970798 /home/leon/Development/data/2017 
java 14965 leon 292r  DIR    8,2  4096 1970799 /home/leon/Development/data/2017/10 
java 14965 leon 293r  DIR    8,2  4096 1970799 /home/leon/Development/data/2017/10 
java 14965 leon 294r  DIR    8,2 36864 1970800 /home/leon/Development/data/2017/10/17 
java 14965 leon 295r  DIR    8,2 36864 1970800 /home/leon/Development/data/2017/10/17 

EDIT 2:私は問題をこの行に隔離することができました:Files.list(dir).collect(Collectors.toList()).size() == 0。これはガベージコレクションではないはずですか?

+0

ファイルを開くコードはありません。なぜファイル記述子を作成するのですか? 'lsof'出力のスニペットを投稿できますか? – Thomas

+0

@トーマスええ、私もその部分を理解していません。私がgetOldestFile()をコメントした場合;ライン、全くリークはありません。 lsof出力スニペットを投稿しました。 – leonz

+0

ああ、それはファイルのファイル記述子を開くのではなく、ディレクトリのためです。あなたはあなたの質問にそれを修正することができます。 – Thomas

答えて

4

Files#list() documentationから:

返されるストリームはDirectoryStreamをカプセル化します。ファイルシステムリソースのタイムリーな処分が必要な場合は、ストリーム操作が完了した後でストリームのcloseメソッドが確実に呼び出されるように、try-with-resources構造を使用する必要があります。

最終的にストリームはガベージコレクションされますが、すぐには処理されません。この場合、自分で管理する必要があります。

+0

close()ガベージを使用して、必要に応じて収集します。どうもありがとう。 – leonz

関連する問題