One of the most important lessons to learn in any language is that borrowed resources must be returned to the operating system.  

For example a file can be opened for reading using an InputStream.  While the input stream is open, the Java Virtual Machine process consumes one “file handle” from the operating system.  Since use of an operating system file handle takes a resource away from other programs, the Operating System quite reasonably restricts each process to a few hundred file handles at a time – a limit that cannot be avoided in your Java code.

This system is much like a library – you borrow a book from the library, open it and start reading.  If you forget to close the book and return it, and do this many many times, eventually the library either suspends your account or runs out of books.  So back to Java.

Normally a close() method tells the Java Virtual Machine that the program has finished with the file and that it can be returned to the operating system.  Depending on the operating system, keeping a file open for reading can prevent temporary directories from being deleted if they contain a file that is still regarded as being read.  Using the library analogy, it's like not being able to move the History wing of the library to another building while there are still books waiting to be returned.  

Writing files is more problematic - when a file is opened by a Java process for writing or appending, some operating systems will lock that file so no other process can read it until close() is called.  While that file is open, not even another Java thread will be able to open that file and read it, and deleting the file may be impossible.

The first example opens a file for reading without any safety features at all:

InputStream in = new FileInputStream(invoiceFilePath);
Invoice invoice = loadInvoiceFile(in);
return invoice;

The coder assumes that when the variable 'in' goes out of scope it will eventually be garbage collected.  The code executes without an error.  It is true that when 'in' is garbage collected it will be closed if still open, so everyone is happy right?  Wrong.

Unfortunately garbage collection can be minutes or hours away, especially when the system is under heavy load.  So the code has a hidden bug; at some time in the future, the Java Virtual Machine may run out of allowed file handles and the program will no longer be able to open other important files.  When your application opens a hundred files a second, a minute can be an eternity.

Under protest, the developer added a close() call to the code:

InputStream in = new FileInputStream(invoiceFilePath);
invoice = loadInvoiceFile(in);
in.close();
return invoice;

So the above example is a little better.  It closes the input stream immediately after use.  However if loadInvoiceFile(in) throws an exception, the in.close() will never be called.

If the invoice loading process has errors, the input stream is again forced to rely on garbage collection to close it, and many thrown Exceptions (like a locked file, or an invalid invoice file format) may cause the application to run out of file handles.

The safe and correct Java way is to use a finally {..} block to guarantee that resources are returned.  

InputStream in = null;
try  
{
    in = new FileInputStream(invoiceFilePath);
    loadInvoiceFile(in);
}
finally 
{
    // this code always gets executed.
    try {if (in != null) in.close();}  
    catch(IOException e){log.error(e);}
}

Linux systems have less stringent restrictions on file locking – you can be reading (e.g. tail -f) a file while one is being written, but it's still good practice to use a finally block to close everything nicely. A base philosophy of Java is that the same code should run on all platforms, so try and avoid breaking that model even if you think your code will always be running on Linux. Running out of file handles is a problem that affects Linux too, so a correct close() call will prevent this from happening.

blog comments powered by Disqus