28

When I remove the for-loop I get an OutOfMemoryError. When I use for-loop I don't get any error.

Can anyone help me to understand this behavior?

public class JavaMemoryPuzzlePolite {
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public void f() {
        {
            System.out.println(dataSize);
            byte[] data = new byte[dataSize];
        }
        for (int i = 0; i < 1; i++) {
            System.out.println("Please be so kind and release memory");
        }
        System.out.println(dataSize);
        byte[] data2 = new byte[dataSize];
    }

    public static void main(String[] args) {
        JavaMemoryPuzzlePolite jmp = new JavaMemoryPuzzlePolite();
        jmp.f();
    }
}
Lii
  • 11,553
  • 8
  • 64
  • 88
snagpal
  • 381
  • 2
  • 2

1 Answers1

30

The f() method is executed in interpreted frame. Interpreted frames behave differently than JIT-compiled frames. Here's how it looks in pseudocode without the for loop:

1. Allocate dataSize bytes of memory
2. Store it into variable slot #1
3. Allocate dataSize bytes of memory
4. Store it into variable slot #1

So you have the OutOfMemoryError on the step #3 as the old byte[] array still resides in variable #1. However adding the for loop (actually adding an i variable) makes thing different:

1. Allocate dataSize bytes of memory
2. Store it into variable slot #1
3. Store 0 to slot #1 (thus byte[] array is now eligible for GC)
4. Do the for loop
5. Allocate dataSize bytes of memory
6. Store it into variable slot #2

Here when you allocate the new array at step #5, the first array can already be garbage collected.

Note that JIT compiler may behave smarter and unlink the first array from the variable as it becomes unused (in your particular case it will not allocate it at all).

Also note that in your particular case the result depends on java compiler. ECJ (Eclipse compiler) is smart enough not to store the first array into variable at all as it's not used. Thus you will not get OutOfMemoryError in ECJ-compiled class even without the for loop.

For more details you can look into bytecode disassembly output provided by javap utility and see how the variable slots are reused.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • can you please explain the step 3 a bit more? `3. Store 0 to slot #1 (thus byte[] array is now eligible for GC)` – asgs Jul 07 '15 at 06:51
  • 3
    @asgs, each local variable has its slot which is assigned by javac compiler. The `data` is assigned to slot #1. Then block ends, so slot can be reused and the next variable will have the slot #1 as well. With for loop the next variable is `i`. Thus `int i = 0` is translated to `iconst_0 / istore_1` which is "Store 0 to slot #1" in my answer. The interpreter does not care if it's actually a new variable, it just replaces the previous value, so it becomes unreferenced. – Tagir Valeev Jul 07 '15 at 06:57
  • @TagirValeev perfect, thanks! Doesn't `iconst_0 / istore_1` mean "store 1 to slot #0" rather than "Store 0 to slot #1". – asgs Jul 07 '15 at 08:32
  • 3
    @asgs, nope. `iconst_0` means "load constant 0 to the top of stack" and `istore_1` means "pop the top of stack value and store it into local variable #1". – Tagir Valeev Jul 07 '15 at 08:43
  • so in this case, adding `data=null` before block exit does help GC! – ZhongYu Jul 07 '15 at 21:21
  • 1
    Since `i` is out of scope after the loop, the next variable `data2` should still go to the same slot (`#1`) as before. – Holger Feb 11 '22 at 08:21