|
|
|
In this article, Jack Shirazi looks into exactly what the JVM does when you create a finalizable object and then have it garbage collected. And it is surprisingly bizarre at times.Published November 2007, Author Jack Shirazi
Page 1 of 2
next page: The Finalizer Lifecycle
In this article, I'm going to have a look into exactly what the JVM does when you create a finalizable object, following its lifecycle through until it gets garbage collected. Note this article only covers what happens in the Sun JVM, other JVMs may differ in procedure.
I'll start with a small class that doesn't cause any finalization, so that we can clearly see the differences. My Test1 class (listing 1) is a simple loop that just creates and dereferences instances of Test1. It runs just as you would expect it to - creating lots of garbage instances, with the garbage collector kicking in periodically to clean up the garbage.
public class Test1 { static long NumberOfCreatedInstances = 0; static long NumberOfDeletedInstances = 0; public Test1() {NumberOfCreatedInstances++;} static public void main(String args[]) { System.out.println("starting...."); for (int i = 0; ; i++) { Test1 obj = new Test1(); obj = null; if (i%10000000 == 0) { System.out.println( NumberOfCreatedInstances-NumberOfDeletedInstances); } } } } Listing 1: The Test1 class
Now I'll change Test1 very slightly by making it finalizable. I've called this class Test2 (listing 2). I've added just one tiny method, highlighted in the code. Doesn't seem like much, I don't even call that method from my code. But as I'm sure you all know, it's the special finalize() method that is called by the garbage collector when the object can be reclaimed.
class Test2 { static long NumberOfCreatedInstances = 0; static long NumberOfDeletedInstances = 0; public Test2() {NumberOfCreatedInstances++;} static public void main(String args[]) { System.out.println("starting...."); for (int i = 0; ; i++) { Test2 obj = new Test2(); obj = null; if (i%10000000 == 0) { System.out.println( NumberOfCreatedInstances-NumberOfDeletedInstances); } } } protected void finalize() { NumberOfDeletedInstances++; } } Listing 2: The Test2 class - the only difference from the Test1 class is the addition of a finalize() method
It seems like there is not much of a difference between Test1 and Test2. But if I run the two programs, (the -verbose:gc option is particularly useful here), then I see very very different activities between the two invocations. Test1 happily sails along, creating objects continuously, interrupted occasionally by very fast young generations GCs - just exactly as you would expect from looking at the code.
Test2, on the other hand, slows down to a crawl in comparison (you may want to limit the heap to 64m with -Xmx64m, or even lower). If you run Test2 on a JVM earlier than a 1.6 JVM, then Test2 will very likely produce an OutOfMemoryError! (It might take a while.) On a 1.6+ JVM, it will probably limp along indefinitely, going quite slowly, or it might produce an OutOfMemoryError, depending on the system you run it on.
You might well be expecting this pattern if you have previously read about or encountered the cost of finalizers. Or maybe it's a surprise. It was certainly a surprise to me when I first saw it - a Java program that has very little code, and is definitely not holding on to more than one object according to the code, produces an OutOfMemoryError! Even those of you who were expecting that might still be surprised at the exact details of what is happening here.
Let's start with Test1. This is a straightforward class, with instances
that have a straightforward lifecycle. In the main() method, a Test1 object
is created in the Eden space of the young generation at new Test1()
(see figure 1). Then the explicit dereference in the very next line
(obj = null;
) eliminates any reference to that object
(actually if that line wasn't there, the dereference would occur in
the next loop iteration when obj
is set to point at
the next Test1 instance, but I wanted to be explicit in the example).
Figure 1: New objects getting created in Eden space
At some point, we have created enough Test1 instances that Eden gets full - and that triggers a young generation garbage collection (usually termed "minor GC"). As nothing points to any of the objects in Eden (or possibly one instance is still referenced depending on when the GC occurs), Eden is simply set to empty very efficiently by the garbage collector (if one object is referenced, that single object will be copied to a survivor space first, see figure 2) And presto, we are very efficiently back to an empty Eden, and the main loop continues with further object creation.
Figure 2: A minor GC empties out Eden
Page 1 of 2
next page: The Finalizer Lifecycle