输入流被GCed

时间:2010-10-13 21:48:31

标签: java garbage-collection fileoutputstream

我知道如果我这样做

copyFromInToOut(new FileInputStream(f1), new FileOutputStream(f2));
System.gc();

它将在那些FileInputStream上运行GC,关闭它们。但是如果我做的话

copyFromInToOut(new BufferedInputStream(new FileInputStream(f1)), new BufferedOutputStream(new FileOutputStream(f2));
System.gc();

在BufferedOutputStream之前是否存在FileOutputStream被GC的危险,而不是导致缓冲区刷新? 我不能调用flush,close,因为这需要更多的步骤。它首先涉及声明一个缓冲的输入流,传递,然后调用close。或者我可以安全地做到这一点吗?

4 个答案:

答案 0 :(得分:8)

请勿明确调用System.gc()。不要依赖终结者做任何事情。特别是如果您不了解垃圾收集的工作原理。可以忽略显式垃圾收集请求,并且终结器可能永远不会运行。

一个编写良好的流copyFromInToOut方法可能会在内部使用自己的缓冲区,因此不必包装输出。

FileInputStreamFileOutputStream声明变量,并在close()块中的每个上调用finally

FileInputStream is = new FileInputStream(f1);
try {
  FileOutputStream os = new FileOutputStream(f2);
  try {
    copyFromInToOut(is, os);
    os.flush();
  } finally {
    os.close();
  }
} finally {
  is.close();
}

答案 1 :(得分:5)

在GCd时,我所知道的任何InputStream实现都不会close()。你必须手动close() InputStream。

编辑:显然,FileInputStream会在我不知道的finalize方法中为你做close(),但是看看其他答案是因为你不应该依赖它。

在上面的两个示例中,您必须同时关闭输入和输出流。对于包装缓冲区案例以及任何包装案例,您只需要在最外层close()上调用InputStream,在这种情况下BufferedInputStream

答案 2 :(得分:2)

分析当你这样做时真正发生的事情很有意思,就是不这样做。

始终明确关闭您的信息流。

(回答你的问题,是的,当gc出现并丢失时,缓冲区中的字节可能没有被刷新到文件i / o流中)

答案 3 :(得分:2)

应使用@ erickson的答案中显示的模式显式关闭Streams。依靠最终化来为你关闭流是非常糟糕的主意

  1. 调用System.gc()是很昂贵的,特别是因为(如果它做了什么)可能会触发完整的垃圾回收。这将导致跟踪堆中每个可到达对象中的每个引用。

  2. 如果您阅读System.gc()的javadoc,您将看到它只是JVM的“提示”以运行GC。 JVM可以自由地忽略提示......这将我们带到下一个问题。

  3. 如果您没有明确地运行GC,可能需要很长时间才能运行GC。即便如此,也无法保证终结器立即运行。

  4. 同时:

    • 所有打开的文件都保持打开状态,可能会阻止其他应用程序使用它们
    • 流中的任何未写入数据仍未写入
    • 你的java应用程序甚至可能会遇到问题,打开其他流程就会耗尽文件描述符插槽。

    最后一个问题就是依靠finalization来处理输出流。如果在流完成时流中有未刷新的数据,则输出流类将尝试刷新它。但这一切都发生在JVM内部线程上,而不是您应用程序的一个线程。因此,如果刷新失败(例如因为文件系统已满),您的应用程序将无法捕获生成的异常,因此将无法报告它......或执行任何恢复操作。

    修改

    回到原始问题,结果是BufferedOutputStream类没有覆盖默认的Object.finalize()方法。这意味着BufferedOutputStrean在被垃圾回收时根本不会被刷新。缓冲区中的任何未写入数据都将丢失。

    这是明确关闭流的另一个原因。实际上,在这种特殊情况下,调用System.gc()不仅仅是不好的做法; 可能导致数据丢失。