|
| ||||
|
Welcome to the 174th issue of The Java(tm) Specialists' Newsletter. I've thoroughly enjoyed the last two weeks with my family in Crete, going to beaches, playing Age of Mythology with my two older kids and getting up early every morning to do exercise with my brother-in-law Costa, a professional gym instructor. As a result, I've done very little Java in the last two weeks, aside from answering the 400+ emails that I received as a result of last month's puzzle. Thank you all for participating in this puzzle - you can stop sending emails now ;-) Java Memory Puzzle ExplainedThe initial JavaMemoryPuzzle was based on a discussion that one of my readers, Dmitry Vyazelenko, had with some friends. They were arguing that you should always set local variables to Here is the JavaMemoryPuzzle code again:
public class JavaMemoryPuzzle {
private final int dataSize =
(int) (Runtime.getRuntime().maxMemory() * 0.6);
public void f() {
{
byte[] data = new byte[dataSize];
}
byte[] data2 = new byte[dataSize];
}
public static void main(String[] args) {
JavaMemoryPuzzle jmp = new JavaMemoryPuzzle();
jmp.f();
}
}
The easiest way to understand why it fails is to look at the disassembled class file, which we can generate using the public void f(); Code: 0: aload_0 1: getfield #6; //Field dataSize:I 4: newarray byte 6: astore_1 7: aload_0 8: getfield #6; //Field dataSize:I 11: newarray byte 13: astore_1 14: return Instruction 0 loads the pointer The public void f() {
byte[] data = new byte[dataSize];
byte[] data2 = new byte[dataSize];
}
would result in byte code that does not reuse the stack frame 1, as we can see with instruction 13: public void f();
Code:
0: aload_0
1: getfield #6; //Field dataSize:I
4: newarray byte
6: astore_1
7: aload_0
8: getfield #6; //Field dataSize:I
11: newarray byte
13: astore_2
14: return
The code block allows the compiler to generate code that reuses the stack frame for the next local variable and would be more akin to the following method f() : public void f() {
byte[] data = new byte[dataSize];
data = new byte[dataSize];
}
When does JavaMemoryPuzzle not fail?Several newsletter fans sent me an email pointing out that certain versions of the JVM, such as BEA JRockit and IBM's JVM, do not fail with the first version. Others pointed out that it also does not fail with the new G1 collector, which you can use with the following VM options: Futhermore, Christian Glencross pointed out that when you compile the JavaMemoryPuzzle class with Java 1.0 or 1.1, it optimizes away the body of method f(), thus becoming: public void f(); Code: 0: return The javac compiler in older versions of Java was a lot more aggressive in its optimizations. It saw that we never used the fields and simply removed them from the code. Joakim Sandström discovered that when you start the class with Christian Glencross also sent me code demonstrating that if you call method f() with a small dataSize often enough, then the JIT compiler will kick in and optimize the code. The large array construction then passes also. In this example, we call the f() method 100.000 times with a small dataSize. The HotSpot compiler will thus in all likelihood pick up that this method is being called a lot and then optimize it for us. Since the optimizing occurs in a separate thread, we need to call the method often enough so that we can be sure that the code has been optimized by the time we call it with the large dataSize. If you still get the OutOfMemoryError, just increase the number. Incidentally, you can also call the method fewer times, for example 10.000 times, and then sleep for a second, giving the JIT compiler time to optimize the code. Make sure that you use the server HotSpot compiler, since the client compiler does not seem to do this optimization:
public class JavaMemoryPuzzleWithHotspotWarmup {
private int dataSize = 0;
public void f() {
{
byte[] data = new byte[dataSize];
}
byte[] data2 = new byte[dataSize];
}
public static void main(String[] args) {
JavaMemoryPuzzleWithHotspotWarmup jmp =
new JavaMemoryPuzzleWithHotspotWarmup();
jmp.dataSize = 10;
for (int i = 0; i < 1000 * 1000; i++) {
jmp.f();
}
jmp.dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);
jmp.f(); // probably no OutOfMemoryError
}
}
As we can see, the HotSpot compiler does a great job of optimizing our code even if we do not explicitely set local variables to Why does the Polite version pass?In our previous newsletter, we then showed a "polite" version of the Java Memory Puzzle, which passes on all JVMs that I know of:
public class JavaMemoryPuzzlePolite {
private final int dataSize =
(int) (Runtime.getRuntime().maxMemory() * 0.6);
public void f() {
{
byte[] data = new byte[dataSize];
}
for(int i=0; i<10; i++) {
System.out.println("Please be so kind and release memory");
}
byte[] data2 = new byte[dataSize];
}
public static void main(String[] args) {
JavaMemoryPuzzlePolite jmp = new JavaMemoryPuzzlePolite();
jmp.f();
System.out.println("No OutOfMemoryError");
}
}
The majority of responses were incorrect and suggested that the for() loop either gave the GC time to do its work during the System.out.println() or that there was some obscure synchronization / JVM / voodoo happening at that point. Some of my readers realised that it had nothing to do with the System..out.println and that a simple public void f();
Code:
0: aload_0
1: getfield #6; //Field dataSize:I
4: newarray byte
6: astore_1
7: iconst_0
8: istore_1
9: iload_1
10: bipush 10
12: if_icmpge 29
15: getstatic #7; //Field System.out
18: ldc #8; //String Please be so kind and release memory
20: invokevirtual #9; //Method PrintStream.println
23: iinc 1, 1
26: goto 9
29: aload_0
30: getfield #6; //Field dataSize:I
33: newarray byte
35: astore_1
36: return
After Instruction 8 has completed, we do not have a strong reference to the first Heinz | ||||
|
Upcoming Events....
|
About Me
I have been writing for the Java specialist community since 2000. It's been fun. It's even more fun when you share this writing with someone you feel might enjoy it. And they can get it fresh each month if they head for www.javaspecialists.eu and add themselves to the list.. | |||
| Copyright Heinz Kabutz 2009 Aristotelous 84, Korakies, Akrotiri, Chania, Crete, 73100, Greece |