Summary
In a recent article, Brian Goetz differentiates between memory and non-memory resources used by a Java application, and shows that garbage collection does not handle non-memory resources. Such resources must be managed by the programmer, and Brian demonstrates a few strategies for how to do this well.
Advertisement
While built-in automatic garbage collection is among the chief reasons for Java's phenomenal success, thinking that GC allows a developer to give no thought to resource management is misleading, and results in faulty applications. In a recent article, Brian Goetz points out that:
The vast majority of resources used in Java programs are objects, and garbage collection does a fine job of cleaning them up. Go ahead, use as many Strings as you want. The garbage collector eventually figures out when they've outlived their usefulness, with no help from you, and reclaims the memory they used.
On the other hand, nonmemory resources like file handles and socket handles must be explicitly released by the program, using methods with names like close(), destroy(), shutdown(), or release().
Brian then divides such nonmemory resources based on the span of time such a resource is used: Those used within a method, and those with arbitrary life-cycles.
Most resources are not held for the lifetime of the application; instead, they are acquired for the lifetime of an activity.
We all know that we should use finally to release heavyweight objects like database connections, but we're not always so careful about using it to close streams[...]. It's also easy to forget to use finally when the code that uses the resource doesn't throw checked exceptions.
As Brian shows, correctly releasing resources often leads to unwieldy Java code, but there is not much a developer can do to simplify this. This is a matter of correct coding, and there is no magic that can flatten out code blocks such as this:
As for managing resources with arbitrary lifecycles:
We're back to where we were with C -- managing resource lifecycles manually. In a server application where clients make a persistent network connection to the server for the duration of a session (like a multiplayer game server), any resources acquired on a per-user basis (including the socket connection) must be released when the user logs out.
[...] Resources with arbitrary lifecycles are almost certainly going to be stored in [...] a global collection somewhere. To avoid resource leaks, it is therefore critical to identify when the resource is no longer needed and remove it from this global collection.
One especially useful piece of advice relates to resource ownership:
A key technique for ensuring timely resource release is to maintain a strict hierarchy of ownership; with ownership comes the responsibility to release the resource. If an application creates a thread pool and the thread pool creates threads, the threads are resources that must be released (allowed to terminate) before the program can exit.
But the application doesn't own the threads; the thread pool does, and therefore the thread pool must take responsibility for releasing them. Of course, it can't release them until the thread pool itself is released by the application.
What are your strategies for managing nonmemory resources in Java? Do you know of any good techniques to ease resource management chores?
The example in Frank's original post was a quote from Brian's article, and I don't really like the look of it. The code you show here is the idiom I always use (I consider it the Java idiom), unless some other idiom is called for by the API. One place where this happened to us is in Hibernate, where the recommended idiom was to do something like:
Session session = SessionMaker.currentSession();
Transaction txn = null;
try {
txn = session.beginTransaction();
// Do stuff
txn.commit();
}
catch (Exception e) {
try {
if (txn != null) {
txn.rollback();
}
}
catch (HibernateException e1) {
// Deal with inability to roll back
}
finally {
throw e;
}
}
finally {
SessionMaker.closeSession();
}
It kind of bugged me that the Hibernate folks recommended an idiom that departed from the usual java idiom, but we follow it.
The programming language D manages resources correctly: it uses GC for memory and RAII for other resources.
The real difference between memory and other resources is that memory references make up a complicated graph, where as other resources do not...hence the difference.
Agreed. You have to release a resource in 'finally'. But the point in Frank Sommers' example also is that you need nested try/finally blocks to release more than one resource.
You can factor out the cleanup code into a reusable function (Frank Sommers' example):
If there was an IOException in the preceeding "real" code, say, a file is locked or an invalid path or a faulty format, the IOException still gets thrown. So I'm only ignoring Exceptions if the first part succeeds.
If, within the finally clause, the already opened file and modified or read file cannot be closed, I have no idea how this exception can occur other than a catastrophe (should I put up a dialog "hey, your disk just crashed"?), in which case I don't think handling the Exception will be worth the effort.
> If there was an IOException in the preceeding "real" code, > say, a file is locked or an invalid path or a faulty > format, the IOException still gets thrown. So I'm only > ignoring Exceptions if the first part succeeds. > > If, within the finally clause, the already opened file and > modified or read file cannot be closed, I have no idea how > this exception can occur other than a catastrophe (should > I put up a dialog "hey, your disk just crashed"?), in > which case I don't think handling the Exception will be > worth the effort.
I can't think of a scenario either off the top of my head, but close() on an output stream will also cause data to be flushed if it hasn't already been. So if close() on an output stream fails, it might mean the outputting you were doing didn't actually complete, and I think that may very well be something you want to handle if you want your system to be "solid."
Good point on close() also doing flush(). I was going to comment that you are safe if you do the flush() up in the try{} part, so that Exceptions in flush get thrown. But I just re-read the javadocs on OutputStream.flush(). Which are real scary...
OutputStream.flush() does nothing! And the general contract on flush() makes no guarantee that the data made it to disk.
Since you can never tell if it "really" worked, one could argue that you may as well ignore Exceptions. But I don't write mission critical apps...