InputStreams being GCed
I know that if I do something like
copyFromInToOut(new FileInputStream(f1), new FileOutputStream(f2));
System.gc();
It will run the GC on those FileInputStream
s, 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 BufferedOut开发者_StackOverflowputStream, 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?
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();
}
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
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)
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:
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.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.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.
精彩评论