The JVM Garbage Collector (GC) is one of those things that can either make your life a happy place or a misery. This article examines the interplay of the common RAII (Resource Assignment Is Initialization) design pattern, or the lack of it in Java, and the GC. Given that the pattern isn’t inherently implementable within the language (at least not in a predictable manner), the requisite workarounds can easily lead the unwary Java developer straight into the realm of self-imposed denial of service.
Discussing the Problem
The RAII design pattern defines a life-cycle for a resource that is explicit and that is owned by the class that hands out, or encapsulates, said resource.
In C++ this is typically performed by allocating an underlying O/S resource in the class constructor and ensuring that it is correctly released in the class destructor. For example, something like:
class foo {
private:
FILE* fp;
public:
foo() : fp(NULL) { }
foo(char* name) : fp(fopen(name, “w”)) { }
virtual ~foo() { if( fp ) fclose(fp); }
};
Users of this class are completely removed from any semantic involved with managing the underlying resource, although of course they are still responsible for managing the memory associated with instances of this class they create.
In contrast, Java developers face no requirement for managing memory associated with objects, but yet they remain tasked with cleaning up whatever resources the object happens to allocate, and there lies the problem for the unwary developer.
Developers that need control over resource lifetime, for example, those creating server applications, are completely responsible for managing the lifetime of each and every resource they create, for example:
public void foo(String name) {
InputStream is = null;
try {
is = new FileInputStream(name);
...
}
finally {
if( is != null )
try { is.close(); }
catch( IOException e ) { }
}
}
That
FileInputStream object is a resource allocator, but courtesy of the delayed execution of the GC, finalization isn’t a reasonable means by which object lifetime can be managed, and so the calling code is instead responsible for lifetime management.
There is no mechanism, after all, to inform the GC that a particular object really needs to be collected, and hence finalized, as soon as its reference count hits zero. Performance considerations have ruled the design parameters for the GC for so long now, and rightly so in most contexts, that to think about redesigning it for one particular design pattern seems laughable.