文件描述符泄漏示例?

14

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

请分享一些博客/代码片段。谢谢!


1
只是打开一堆文件或端口而不关闭吗?几千个后,窗口就会停止给它们。其他操作系统也类似。 - Ordous
@Ordous,感谢您的快速回复。您能否请发布一段可工作的代码片段? - Vikasdeep Singh
3个回答

14

Dalvik的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中,每当我们创建一个新的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

如果我们强制运行垃圾回收,我们可以看到大多数对象被返回:

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

剩余的两个描述符存储在“修订版”中。
我在我的Ubuntu机器上运行了这个命令,但如果在Android上运行,则输出看起来类似。

-1
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
1)这个问题被称为“文件描述符泄漏示例”,而不是“如何正确关闭流”。你甚至没有用最好的方法来关闭它。 2)只需在while(1)循环中包裹此代码不能算作“泄漏”(即“意外永久丢失资源”)。 3)有很大的可能性,堆栈跟踪创建后将会进行GC处理,这将快速清理大多数“泄漏”的流。 - user1643723
1
很抱歉,我想我没有理解你的问题。然而,考虑到你已经发布了一个月的悬赏,并且没有人回答它,我可能不是唯一一个不明白你在问什么的人。祝你好运。 - Joboodi

-1
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

  } 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接