1. ホーム
  2. java

[解決済み] ファイルディスクリプタの漏洩例?

2022-02-14 13:10:39

質問

Android におけるファイル記述子のリークを実証する良い例はありますか?どこかで読んだのですが、例えば次のようにストリームを閉じないと発生するそうです。 FileInputStream または FileOutputStream しかし、それを実証する良い参考例が見つかりませんでした。

ブログ/コードスニペットを共有してください。

どのように解決するのですか?

なぜなら、Dalvikの ファイルインプットストリーム 意志 ガベージコレクトされたときに自分自身を閉じる (これはOpenJDK/Oracleにも言えることです)実際にファイルディスクリプタをリークすることは思ったより少ないです。もちろん、ファイルディスクリプタはGCが実行されるまでquot;leaked"なので、プログラムによっては再取得されるまでに時間がかかるかもしれません。

より恒久的なリークを実現するには、メモリ上のどこかでストリームへの参照を保持することで、ガベージコレクションされないようにする必要があります。

以下は、1秒ごとにプロパティファイルをロードし、それが変更されるたびに追跡する短い例です。

public class StreamLeak {

    /**
     * A revision of the properties.
     */
    public static class Revision {

        final ZonedDateTime time = ZonedDateTime.now();
        final PropertiesFile file;

        Revision(PropertiesFile file) {
            this.file = file;
        }
    }

    /*
     * Container for {@link Properties} that implements lazy loading.
     */
    public static class PropertiesFile {

        private final InputStream stream;
        private Properties properties;

        PropertiesFile(InputStream stream) {
            this.stream = stream;
        }

        Properties getProperties() {
            if(this.properties == null) {
                properties = new Properties();
                try {
                    properties.load(stream);
                } catch(IOException e) {
                    e.printStackTrace();
                }
            }
            return properties;
        }

        @Override
        public boolean equals(Object o) {
            if(o instanceof PropertiesFile) {
                return ((PropertiesFile)o).getProperties().equals(getProperties());
            }
            return false;
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        URL url = new URL(args[0]);
        LinkedList<Revision> revisions = new LinkedList<>();
        // Loop indefinitely
        while(true) {
            // Load the file
            PropertiesFile pf = new PropertiesFile(url.openStream());
            // See if the file has changed
            if(revisions.isEmpty() || !revisions.getLast().file.equals(pf)) {
                // Store the new revision
                revisions.add(new Revision(pf));
                System.out.println(url.toString() + " has changed, total revisions: " + revisions.size());
            }
            Thread.sleep(1000);
        }
    }
}

レイジーローディングのため InputStream の中で PropertiesFile を作成するたびに保持されます。 リビジョン また、ストリームを閉じることがないため、ここでファイルディスクリプタを漏らすことになります。

さて、これらの開いているファイルディスクリプタは、プログラムが終了するとOSによって閉じられますが、プログラムが実行されている限り、ファイルディスクリプタをリークし続けることが、以下のようにしてわかります。 lsof :

$ lsof | grep pf.properties | head -n 3
java    6938   raniz   48r      REG    252,0    0    262694 /tmp/pf.properties
java    6938   raniz   49r      REG    252,0    0    262694 /tmp/pf.properties
java    6938   raniz   50r      REG    252,0    0    262694 /tmp/pf.properties
$ lsof | grep pf.properties | wc -l    
431

そして、強制的にGCを実行させると、これらのほとんどが返されることがわかります。

$ jcmd 6938 GC.run
6938:
Command executed successfully
$ lsof | grep pf.properties | wc -l
2

残りの2つのディスクリプタは リビジョン s.

私はUbuntuマシンでこれを実行しましたが、Androidで実行しても同じような出力になると思います。