【问题标题】:File descriptor leak example?文件描述符泄漏示例?
【发布时间】:2015-09-23 03:09:50
【问题描述】:

有没有什么好的例子可以证明 Android 中的文件描述符泄漏?我在某处读到,如果我们不关闭流,例如FileInputStreamFileOutputStream,就会发生这种情况,但我找不到任何好的参考示例来演示它。

请分享一些博客/代码sn-p。谢谢!

【问题讨论】:

  • 只打开一堆文件或端口而不关闭?在几千个窗口停止给他们之后。在其他操作系统中类似。
  • @Ordous,感谢您的快速回复。能否请您发布它的工作代码sn-p

标签: java android memory-leaks file-descriptor memory-leak-detector


【解决方案1】:

因为 Dalvik 的 FileInputStreamclose itself when it is garbage collected(对于 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 中,每当我们创建新的 Revision 时都会保留它,因为我们从来没有关闭我们将在此处泄漏文件描述符的流。

现在,当程序终止时,这些打开的文件描述符将被操作系统关闭,但只要程序正在运行,它就会继续泄漏文件描述符,这可以通过使用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

剩下的两个描述符是存储在Revisions中的。

我在我的 Ubuntu 机器上运行它,但如果在 Android 上运行,输出看起来会相似。

【讨论】:

    【解决方案2】:
    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() 行,然后在您的 catch 块中放置一个 break;这样当它爆炸时,你就会跳出你的无限循环。

    【讨论】:

    • 1) 这个问题被称为“文件描述符泄漏示例”,而不是“如何正确关闭流”。你甚至不是最好的方法。 2) 只需将此代码包装在 while(1) 循环中 不符合“泄漏”的条件(如“意外永久丢失资源”) 3) 很有可能,该堆栈跟踪创建之后将进行 GC 传递,这将快速完成大部分“泄漏”流。
    • 对不起,我想我没听懂你的问题。但是,考虑到您已经有一个月的时间提出了这个问题,并且没有人回答它,我可能不是唯一一个不明白您的要求的人。祝你好运。
    【解决方案3】:
    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
    
      } 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-15
      • 2014-09-20
      • 2020-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      相关资源
      最近更新 更多