/* 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 |
Java VisualVM Showing the Car Object Remaining in Memory |
Only One Destructor Called After Perform GC |
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.
The console also confirms this
No Person or Car Objects Remain 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.
Console Showing All Destructors Called
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 IntelliJAll 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.
Local Variables in main()
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.
And, as in the first version, the synchronized() keyword results in an ASTORE 2 over the Car slot.
RefTestMain Will Use Person's Slot 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,
Synchronized Moving Operand Stack On Reclaimed LOCALVARIABLE
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