Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Tuesday, April 12, 2016

Java Memory Management and Code Blocks

You can put {} anywhere in a Java method to group a set of statements in an isolated scope.  Usually, the {} syntax is used with methods (ex, void foo() {}) or try / catch statements (ex, try { ... ) .  If you're working with a code generator, you might use a block to repeat a set of statements without worrying about name collisions.  For example,

/* parse first item */
{
  Parser p = new Parser("firstitem.xml");
  ...
}

/* parse second item */
{
  Parser p = new Parser("seconditem.xml");
  ...
}

In addition to the namespace protection, you would also expect the memory associated with the code blocks to be reclaimed.  Most likely it will, but this blog post will demonstrate that it may not happen at the precise instant you expect.

This program demonstrates how local variables can retain their memory even after falling out of scope.

I have 2 similarly defined classes: Car and Person.  They're container objects with a single field, a constructor, and a finalize method.

static class Car {
    String make;
    public Car() { System.out.println("created Car"); }
    @Override    protected void finalize() throws Throwable {
        System.out.println("destroying Car");
    }
}

static class Person {
    String name;
    public Person() { System.out.println("created Person"); }
    @Override    protected void finalize() throws Throwable {
        System.out.println("destroying Person");
    }
}
The classes belong to a class RefTestMain.
In the main(), I create objects of type Car and Person isolated in a separate block.  Notice the RefTestMain object declared before the block.
public static void main(String[] args) throws Exception {

    RefTestMain app = new RefTestMain();

    {
        Person p = new Person();
        p.name = "Carl";

        Car c = new Car();
        c.make = "Ford";

        System.out.println("c.make=" + c.make + ", p.name=" + p.name );
   }

I close out the main by creating an instance of RefTestMain and infinitely waiting on the object so that the app doesn't close and I can look at things with Java VisualVM.
    synchronized(app) {
        app.wait();
    }
}  // end main()

Looking at this in Java VisualVM, I see that the Person and Car objects are still in memory despite falling out of scope.

Java VisualVM After Starting Program
Pressing the Perform GC button after several seconds reclaims one of the objects.  However, the Car instance remains.
Java VisualVM Showing the Car Object Remaining in Memory
This is additionally verified in the IDE Console.
Only One Destructor Called After Perform GC
If I make one small modification to my program, the GC can reclaim both objects.  I move the RefMainTest declaration below the code block.
    RefTestMain app = new RefTestMain();  // moved
    synchronized(app) {
        app.wait();
    }
}  // end main()
Now, when I run the program, wait a few seconds, then press Perform GC, both objects are reclaimed.

No Person or Car Objects Remain
The console also confirms this
Console Showing All Destructors Called
Person and Car are small objects, but if they were huge and being retained while other memory-intensive methods calls are being made, then you might run into a memory error or long GC operations.
Analysis - First Version with RefTestMain Declared Early
To understand the difference that the placement of the RefTestMain instantiation made, look at the following bytecode.  This is from the first version of the program which declared the RefTestMain object at the start of the main().  I'm using the ASM plugin on IntelliJ
Local Variables in main()
All of the variables both within and outside of the block are LOCALVARIABLE.  Notice the indexes that appear to the right.  If I look a little earlier in the bytecode to the point at which the synchronized() statement is issued, I see that #2, the index for the Person object, is the target of an ASTORE operation.  At this point, the Person object's block has ended and its LOCALVARIABLE location is able to be reused.  This is shown in the results which indicate that the Person block is reclaimed after the GC.
The Car instance remains because there is no second object (we're at the end of the program) that needs to take its place.  So, as new LOCALVARIABLEs are defined, the memory can be reused and if no new objects are created, you didn't need the memory anyway. That's no quite right, because there could be any number of method calls in between the end of the code block and the synchronized().  While these calls are underway, the Person and Car objects would remain in memory.
Analysis -- Second Version with RefTestMain After Code Block
This version puts the RefTestMain instantiation after the code block.  The screenshot lists the LOCALVARIABLES.  Notice the non-unique indexes showing the recycling of the LOCALVARIABLE slots.  This makes the objects able to be reclaimed.

RefTestMain Will Use Person's Slot
And, as in the first version, the synchronized() keyword results in an ASTORE 2 over the Car slot.
Synchronized Moving Operand Stack On Reclaimed LOCALVARIABLE
So, in this final version, Person and Car are allocated in a block as LOCALVARIABLES.  The block completes.  RefTestMain is allocated and reuses the LOCALVARIABLE index for Person.  synchronized() is called and stores a copy of RefTestMain in the LOCALVARIABLE index for Car.  After a Perform GC is executed, the memory is reclaimed. This program was a special case where the number of variables in the code block (Car + Person = 2) matches the number of variables after the code block (RefTestMain + synchronized ASTORE = 2).  If there were more declarations in the code block,
Developer Guidance
I'm not suggesting that developers obsess about the ordering of their statements for optimal memory management.  You should always concentrate on functional requirements first and readability (maintainability) second.  This is a troubleshooting post offering something to try if you find yourself with a memory problem. One quick fix is to wrap the code block into a method call.  This allowed the memory to be freed when the method call returned.  Another quick fix is to set each allocated object in the code block to null (p = null; c=null;).  I wouldn't start out with either of these, especially the null-setting, but they're something worth trying if you're experimenting.

I tested this program on Windows 8, Mac, and Ubuntu using Java 8u77. I also found similar results on Ubuntu with Java 7u80.

No comments:

Post a Comment