Abstract: For years, programmers have spread the misinformation that it was necessary to set local variables to null to avoid memory leaks. Fortunately Java is smart enough to do that without our help.
Welcome to the 60th edition of The Java(tm) Specialists' Newsletter sent to 5150 Java Specialists in 91 countries. Thank you so much for your support this year, writing this newsletter, and especially reading your replies, has been an absolute pleasure. One thing that excites me is seeing famous names pop up on my reader list. One of these, Jack Shirazi, a household name in Java performance circles and author of Java Performance Tuning [ISBN 0596000154] , has been watching our newsletter for about a year and Jack and I have often had interesting discussions about the finer details of Java performance. This month Jack decided to interview me, you can read the interview on his website if you would like to.
Today Jack sent me an article for our newsletter, and I am honoured to publish it. Thanks Jack for this excellent article, we all really appreciate the time and effort you took in writing it for us.
After mentioning in my last newsletter how fantastic South Africa is, I have seen a dramatic strengthening of our currency. Ours is the best performing currency against the US$ in the last 12 months. I have come to the conclusion that someone on my mailing list has so much money that they can influence the economy of our country ;-)
Enough of my musing, over to Jack Shirazi:
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
At JavaPerformanceTuning.com, we have a monthly newsletter which includes a roundup of recent performance related discussions from various discussion groups. (We also have many more columns, covering almost all recent Java performance related activity, including interviews - last month we interviewed none other than our excellent host, Heinz). The discussions we report are quite interesting, and sometimes a really fascinating one pops up.
One such discussion at Javagaming.org considered whether setting variables to null helps garbage collection in any significant way. After a little inconclusive discussion, a Sun engineer jumped in with a really interesting example (Javagaming.org is a Sun owned discussion board, run by a couple of Sun engineers, and other Sun engineers sometimes lurk around).
This engineer pointed out that if Eden was full, and an object was about to be created, then if sufficient space could be freed in Eden, the object could be created there with no more work. But if sufficient space could NOT be freed, then existing objects in Eden had to be promoted to old space before the new object could be created. It could be that the application was finished with an object, but it had not yet gone out of scope, so could not be immediately garbage collected. Explicitly nulling the variable referencing that object would make a difference.
For those of you not up to spec on HotSpot GC, the heap is divided into several spaces: Eden and two survivor spaces (the three spaces collectively make up the young generation space) and an old generation space. Objects are created in Eden, and most objects die there and are reclaimed quickly. The two survivor spaces are for copying live objects so that young objects can remain in the young generation space for a time. If objects get too old, or young generation space gets full up, objects get promoted to the old generation space. If you need more detail, try this article Tuning Garbage Collection with the 1.3.1 Java Virtual Machine.
This being a gaming discussion board, the code example the engineer gave was relevant to animation, and a variation of his example follows here.
import java.awt.Image; import java.awt.image.BufferedImage; public class ImageGarbage1 { private static Image img; //IMPORTANT 1 public static void main(String args[]) { // Get one numerical argument which specifies size of image int imageSize = Integer.parseInt(args[0]); long startTime = System.currentTimeMillis(); int imgIndex = 0; long loopIndex = 0; while (true) { //img = null; //IMPORTANT 2 // Create an image object img = new BufferedImage(imageSize, imageSize, BufferedImage.TYPE_INT_RGB); long endTime = System.currentTimeMillis(); ++loopIndex; // We print stats for every two seconds if (endTime - startTime >= 2000) { // Images created and disposed if per second long ips = (loopIndex / ((endTime - startTime) / 1000)); System.out.println("IPS = " + ips); startTime = endTime; loopIndex = 0; } } } }
Basically, this class repeatedly creates and discards images, measuring the rate of object creation, and printing that rate every couple of seconds. A single command line argument allows the size of the object to be specified.
On my desktop, I get the following results on running this:
COMMAND: java -version java version "1.4.1_01" Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-b01) Java HotSpot(TM) Client VM (build 1.4.1_01-b01, mixed mode) COMMAND: java ImageGarbage1 361 IPS = 14 IPS = 18 IPS = 19 IPS = 19
Your machines are probably a darn sight more powerful than my old desktop [hk: goodness me, even my 2 year old notebook is faster ;-], so I'm sure you'll get larger numbers. You need to control-C the process to stop it, otherwise it runs forever. Now let's look at what happens if I uncomment the line labelled "IMPORTANT 2", which simply adds a null assignment to the static variable before creating the image object:
COMMAND: java ImageGarbage2 361 IPS = 116 IPS = 160 IPS = 164 IPS = 165 IPS = 151
Yes, that's right, the null assignment made the test run an order of magnitude faster! Well I guess that's a pretty definitive answer to the question of whether nulling variables makes a difference. What is happening in the first test (ImageGarbage1) is that the image is created in Eden, and when the next image is created, the first image is still hard referenced by the application, so the GC has to promote the object to the old generation to make space for the new object. In the second test (ImageGarbage2), the inserted null reference means that the existing image in Eden is not referenced by the application, and can be discarded.
However, before you go off changing your coding style, I should point out that this is a specially constructed case. The image size was carefully chosen to fill Eden. Look what happens when we increase the image size slightly, even with the null assignment:
COMMAND: java ImageGarbage2 362 IPS = 13 IPS = 18 IPS = 18 IPS = 19
In this last test, the image is too big to fit into the young generation, and gets created directly in the old generation each time. Which means that a full mark-sweep GC of the old generation is needed to reclaim the space, rather than the much faster copying GC of the young generation. And if we choose a much smaller size, so that several images fit into Eden, then each subsequent image assignment releases the reference to the previous image, so allowing them all to be collected in Eden:
COMMAND: java ImageGarbage1 100 IPS = 759 IPS = 1487 IPS = 1490 IPS = 1490 COMMAND: java ImageGarbage2 100 IPS = 810 IPS = 1611 IPS = 1621 IPS = 1613
Let's make one more change. I'll go back to the original version of the test, with the null assignment commented out. But this time I'll change the static variable definition (the line labelled "IMPORTANT 1") into a local variable in the main() method. Now look what happens when I run the test with the image just fitting into Eden:
COMMAND: java ImageGarbage3 361 IPS = 113 IPS = 157 IPS = 165 IPS = 166 IPS = 156
We get back to the same performance as the test with the null assignment, even though we are NOT making that null assignment this time. In this case, the compiler is intelligent enough to work out the scope of the image object, and has dereferenced it before the next assignment, so allowing it to be reclaimed before the next image is created.
Finally, putting together what we've seen, it's easy to work out that Eden's size was the real problem with the original test. So let's get back to that original case, and simply alter the size. Here I simply set the initial heap size larger than the default, which makes Eden proportionately larger:
COMMAND: java -Xms16M ImageGarbage1 361 IPS = 78 IPS = 129 IPS = 120 IPS = 131
And the same test with the slightly larger image, that previously had to be created in old generation space:
COMMAND: java -Xms16M ImageGarbage1 362 IPS = 73 IPS = 120 IPS = 121 IPS = 121
So there is no real need to change your coding style. Tuning the garbage collection is probably a more sensible solution. Does nulling variables improve garbage collection? Maybe, sometimes.
Jack Shirazi is the author of "Java Performance Tuning" (O'Reilly), and director of JavaPerformanceTuning.com. JavaPerformanceTuning.com lists three thousand performance tuning tips and publishes a monthly newsletter with all the latest Java performance news, tips, discussions, and more. See https://www.javaperformancetuning.com/
--Jack Shirazi
jack@JavaPerformanceTuning.com
https://www.javaperformancetuning.com
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.