It depends on the smartness of the compiler and the garbage collector - when they decide that the data and data2 arrays are ready to be GCed.
First funny thing I have noticed is that when I ran the fist code under Eclipse, all went fine and there was no OOME. Only compiling with Sun's javac caused OOME. So I had a look at the bytecode then - and that's the source of the problem;
I renamed the class to test.Test. So running javap -c test.Test gave:
Let's have a look at the f() method, which causes OOME when allocating byte2: first, a new array of 0.6 of max memory is created and assigned to the #1 variable (astore_1), which in our source is the data var. Then, another array of 0.6 of max memory is allocated (thus requiring 1.2 of max memory, causing OOME) and assigned to the same #1 variable (because the compiler realizes that the previous value will never be used), which this time is the data2 var. However, the memory is allocated first and only then the previous array is disposed of. So for a moment both arrays are considered valid and required and the GC cannot get rid of the first one to make place for the second one.
Now let's see why the Eclipse compiler does a better job:
Here, in the f() method, the compiler realizes immediately that both variables - data and data2 anre never used and right after creating the array it removes the reference to it (pop) without even assigning it to any local variable. So it allocates the 0.6 of max memory, frees it right after that and allocates again and frees again. There is always enough memory available.
Now, when we add the if statement, that's what happens in the f() method:
Eclipse compiler does just the same - pops the unused arrays right after creation.
Here the code and the reason why it works is a bit more complicated. The arrays are not popped, but the data variable is out of scope when the execution reaches i assignment, so the variable #1 which at first holds the data array is overwritten with the value of the i variable (see astore_1 which puts the reference to the data array to variable #1 and right after that istore_1 which puts 0 (iconst_0) into the same variable) so the GC can dispose of the big data array and make place for data2.
I must say that it's not the first time that Eclipse produces a better code than Sun in a simple case ;>