views:

77

answers:

4

I know that if I do something like

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

It will run the GC on those FileInputStreams, closing them. But if I do

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

Is there any danger that the FileOutputStream will be GCed before the BufferedOutputStream, not causing the buffer to flush? I can't call flush, close, because that takes more steps than this. It would first involve declaring a bufferedinputstream, passing, then calling close. OR am I safe to do this?

+5  A: 

No InputStream implementation that I am aware with will close() for you when it is GCd. You MUST close() the InputStream manually.

EDIT: Apparently FileInputStream does close() for you in a finalize method which I wasn't aware of, but see other answers for the reason why you shouldn't rely on this.

In both your examples above you must close both the Input and Output streams. For the wrapped buffer case, and for any wrapped case, you should need only call close() on the outer-most InputStream, in this case BufferedInputStream

Mike Q
FileInputStream will close itself in its `finalized()` method
irreputable
@irreputable Where did you see that `FileInputStream` closes itself?
Kin U.
While you indeed should close() them yourself(you've no control over when an object is gc'ed, even if you call System.gc()), This is wrong. e.g. OpenJDK does .close() in its finalize() of a FileInputStream.
nos
@Kin Ueng - it is in the Java 6 source code. Take a look if you don't believe us.
Stephen C
@Mike Q - the first paragraph is incorrect. FileInputStream (for example) *will* close a stream that is garbage collected. But it is a really bad idea to rely on this; see my answer.
Stephen C
+2  A: 

well it's interesting to analyze what's really happening when you do that, just don't do that.

always close your streams explicitly.

(to answer your question, yes, bytes in buffers may not have been flushed to file i/o streams when gc occurs and they are lost)

irreputable
+7  A: 

Don't call System.gc() explicitly. Don't rely on finalizers to do anything. Especially if you don't understand how garbage collection works. Explicit garbage collection requests can be ignored, and finalizers might never run.

A well-written copyFromInToOut method for streams is likely to use its own buffer internally, so wrapping the output should be unnecessary.

Declare variables for the FileInputStream and FileOutputStream, and invoke close() on each in a finally block:

FileInputStream is = new FileInputStream(f1);
try {
  FileOutputStream os = new FileOutputStream(f2);
  try {
    copyFromInToOut(is, os);
    os.flush();
  } finally {
    os.close();
  }
} finally {
  is.close();
}
erickson
One minor comment. Closing close() without guarding for the IOException in a finally block can cause issues if close() decides to throw an Exception when the try block already has. (Or more generally, never allow an exception to be thrown by code in finally {} )
Mike Q
I wouldn't say that it "causes issues". What happens is that the result of the block changes from the exception that was thrown in the `try` block to the exception thrown by `close()`. Sometimes, this isn't desirable. In many cases, you just don't care. Java 7's ARM feature will make it easy to handle this in a consistent way.
erickson
+2  A: 

Streams should be closed explicitly using the pattern shown in @erickson's answer. Relying on finalization to close streams for you is a really bad idea:

  1. Calling System.gc() is expensive, especially since (if it does anything) to is likely to trigger a full garbage collection. That will cause every reference in every reachable object in your heap to be traced.

  2. If you read the javadocs for System.gc() you will see that it is only a "hint" to the JVM to run the GC. A JVM is free to ignore the hint ... which takes us to the next problem.

  3. If you don't run the GC explicitly, it might be a long time until the GC runs. And even then, there's no guarantee that finalizers are run immediately.

In the mean time:

  • all open files stay open, possibly preventing other applications using them
  • any unwritten data in the streams remains unwritten
  • your java application might even run into problems opening other streams do to running out of file descriptor slots.

And there's one last problem with relying on finalization to deal with output streams. If there is unflushed data in the stream when it is finalized, an output stream class will attempt to flush it. But this is all happening on a JVM internal thread, not one of your application's threads. So if the flush fails (e.g. because the file system is full), your application won't be able to catch the resulting exception, and therefore won't be able to report it ... or do anything to recover.

EDIT

Returning to the original question, it turns out that the BufferedOutputStream class does not override the default Object.finalize() method. So that means that a BufferedOutputStrean is not flushed at all when it is garbage collected. Any unwritten data in the buffer will be lost.

That's yet another reason for closing your streams explicitly. Indeed, in this particular case, calling System.gc() is not just bad practice; it is also likely to lead to loss of data.

Stephen C