|
The Java Specialists' Newsletter
Issue 060 2002-11-29
Category:
Performance
Java version: Nulling variables and garbage collectionby Jack Shirazi
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 ,
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:
Would you like to really understand Java concurrency? Join us for an
in-depth study of how threading works in Java. During the course,
you will learn how to write correct and fast multi-threaded Java code.
Please
click here if you would like to learn more. Jack Shirazi: Nulling variables and garbage collection
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 these two URLs:
Java World
and Sun Microsystems.
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 http://www.JavaPerformanceTuning.com/
--Jack Shirazi
jack@JavaPerformanceTuning.com
http://www.JavaPerformanceTuning.com
Performance Articles
Related Java Course
Discuss at The Java Specialist Club
|