问题
Could someone explain why this program throws a OutOfMemoryError
when the for
loop is commented out? If it is uncommented this runs fine.
The exception thrown is:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
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 < 10; 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();
}
}
回答1:
I've investigated this with many different types of code snippets that can be inserted where your comment is and the only type of code that will not cause an OutOfMemoryError
is code that assigns some value to a local variable.
This is the explanation that makes the most sense to me:
When you have
byte[] data = new byte[dataSize];
The bytecode instructions are
12: newarray byte
14: astore_1
Where newarray
creates a new array and astore_1
stores a reference to it in a local variable 1.
After this, the scope of that variable is lost, but the bytecode doesn't say anything about its value being cleared, so there's a reference to that object remaining in the stack frame. This specific garbage collector deems it reachable even if the code itself cannot reach it.
If instead you try to assign another local variable, like
byte i = 1;
then the corresponding byte code instructions are something like
15: iconst_1
16: istore_1
where iconst_1
stores the value 1 on the stack and istore_1
stores that value in the variable 1, which seems to be the same variable as before. If it is, then you are overwriting its value, the reference to the byte[]
object is lost, and the object then "becomes" eligible for garbage collection.
The final proof
Compile this code with the -g
option
public class Driver {
private static final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);
public static void main(String[] args) throws InterruptedException {
{
System.out.println(dataSize);
byte[] data = new byte[dataSize];
}
byte i = 1;
System.out.println(dataSize);
byte[] data2 = new byte[dataSize];
}
}
and then run javap -c -l Driver
. You will see a LocalVariableTable
like so
LocalVariableTable:
Start Length Slot Name Signature
15 0 1 data [B
0 33 0 args [Ljava/lang/String;
17 16 1 i B
32 1 2 data2 [B
where slot is the index in astore_1
and istore_1
. So you see, the reference to the byte[]
is cleared when you assign a new value to the local variable. Even if the variables have different types/names, in bytecode, they are stored in the same place.
回答2:
As the answer linked to by user Sotirios Delimanolis in the comments suggests, this has to do with the scope of the first data
array. Simplifying a bit, the code
{
byte[] data = new byte[bigNumber];
}
byte[] data = new byte[bigNumber];
compiles to (simplified and using English)
1: make an array of size bigNumber
2: store a reference to the created array into the 1st local variable
3: make an array of size bigNumber
4: store a reference to the created array into the 1st local variable
This runs out of memory since at step three 60% of the memory is already occupied and we are trying to create an array that takes up 60% more memory. The old byte array cannot be garbage collected because it is still referenced in the first local variable.
However the code
{
byte[] data = new byte[bigNumber];
}
someOtherVariable = somethingSmall;
byte[] data = new byte[bigNumber];
compiles to
1: make an array of size bigNumber
2: store a reference to the created array into the 1st local variable
3: make somethingSmall
4: store a reference to somethingSmall into the 1st local variable
5: make an array of size bigNumber
6: store a reference to the created array into the 2nd local variable
Here at step four the initial large byte array is dereferenced. So when you try to make another large array at step five it succeeds because it can garbage collect the old array to make room.
回答3:
When you have the for loop uncommented, while executing the loop, JVM gets enough time to release the memory allocated for data
since the scope of the variable has ended and the variable became unreachable.
When you comment out the for loop, JVM won't get much time to free the memory used by the varibale data
来源:https://stackoverflow.com/questions/21437699/outofmemoryerror-when-seemingly-unrelated-code-block-commented-out