Пример утечки дескриптора файла?

есть ли хороший пример, демонстрирующий утечку дескриптора файла в Android? Я где-то читал, что это происходит, если мы не закрываем потоки, например FileInputStream или FileOutputStream но я не мог найти любой хороший пример, который демонстрирует это.

пожалуйста, поделитесь фрагментом блога/кода. спасибо!

3 ответов


, потому что FileInputStream будет закройте себя, когда это мусор, собранный (это также верно для OpenJDK/Oracle) это менее распространено, чем вы думаете, на самом деле утечки дескрипторов файлов. Конечно, файловые дескрипторы будут "просачиваться" до тех пор, пока GC не запустится, поэтому в зависимости от вашей программы может потребоваться некоторое время, прежде чем они будут исправлены.

выполнить более постоянного, утечки вам придется предотвратить поток от мусора по сохраняя ссылку на него где-то в памяти.

вот короткий пример, который загружает файл свойств каждые 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 котором будут храниться всякий раз, когда мы создаем новый редакция и поскольку мы никогда не закрываем поток, мы будем пропускать файловые дескрипторы здесь.

Теперь эти открытия файла дескрипторы будут закрыты ОС, когда программа завершится, но до тех пор, пока программа работает, она будет продолжать пропускать файловые дескрипторы, как видно из использования 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

оставшиеся два дескриптора-это те, которые хранятся в редакцияs.

я запустил это на своей машине Ubuntu, но выход будет выглядеть похожие, если работать на Android.


InputStream in;
try {
    in = new BufferedInputStream(socket.getInputStream());

    // Do your stuff with the input stream
} catch (Exception e) {
    // Handle your exception
} finally {
    // Close the stream here
    if (in != null) {
        try {
            in.close();
        } catch (IOException e) {
            Log.e(TAG, "Unable to close stream: " + e);
        }
    }
}

идея состоит в том, чтобы закрыть файловый дескриптор в finally блок. Завершите ли вы успешно или произойдет исключение, дескриптор файла будет правильно закрыт.

теперь, если вы ищете что-то, чтобы продемонстрировать, как это сделать неправильно, просто оберните этот код в while(1) цикл, прокомментируйте in.close() line, и поставить break; в вашем блоке catch, так что, когда он взорвется, вы вырветесь из своего бесконечного цикла.


InputStream in;

try {

    in = new FileInputStream(new File("abc");


    in.read(); // Do some stuff with open fileinputstream
    // If an exception is generated, inputstream object will not be closed
    // as the next statement will not be executed, instead jumping to 
    // the catch block. this will cause a leak of the fd assigned to file 
    // "abc" while opening it
    in.close()' 
  } catch (Exception e) {

    // Handle your exception

  }