views:

234

answers:

4
+2  Q: 

Java memory puzzle

Suppose I have following code

package memoryleak;

public class MemoryLeak {

    public static int size;

    static {
        size = (int) (Runtime.getRuntime().maxMemory()*0.6);
    }

    public static void main(String[] args) throws InterruptedException {
        {
            byte[] data1 = new byte[size];
        }

        byte[] data2 = new byte[size];
    }
}

This code generates OutOfMemoryError. You can make this code work with one variable allocation (which rewrite stack frame used by first array and make array available for garbage collecting). This puzzle explained here.

{
    byte[] data1 = new byte[size];
}
int i = 0;
byte[] data2 = new byte[size];

The question is: why following code still doesn't work?

Object o = new Object();
synchronized (o) {
    byte[] data1 = new byte[size];
}
int i = 0;
byte[] data2 = new byte[size];

And following works:

Object o = new Object();
synchronized (o) {
    byte[] data1 = new byte[size];
}
int i = 0;
synchronized (o) {
    byte[] data2 = new byte[size];
}
A: 

You're relying on the GC to collect before your instantiation?

couldn't you do

Object o = new Object();
byte[] data1 = new byte[size];
GC.Collect()
byte[] data2 = new byte[size];
Spence
The GC always collects before throwing a OutOfMemoryError, as described in it's documentation: http://java.sun.com/javase/6/docs/api/java/lang/OutOfMemoryError.html So that at least is a reliable behaviour. Your code doesn't have to collect data1 reliable, because it is not out of scope and needs further optimization, that it is clear, that data1 can be safely reclaimed.
Mnementh
He should not have to, and it should not do him any good if he did. The JVM should try really hard to free memory before throwing an OutOfMemoryError, so the garbage collector will have been invoked automatically, and even in full-sweep mode.
Thilo
*The GC always collects before throwing a OutOfMemoryError, as described in it's documentation: java.sun.com/javase/6/… So that at least is a reliable behaviour.*Interesting: I didn't know this. But it still isn't deterministic, because the garbage collector doesn't guarantee that it will collect all unused objects in one cycle. As http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html#par_gc.oom explains.
+5  A: 

My bet is that synchronized adds an element to the frame, causing data1 to move up a slot and not get clobbered by i. synchronized needs to unlock the same object that it locked, even if the local/field changes.

The synchronized code would look something like this:

Object $sync = o;
$sync.lock();
try {
    byte[] data1 = new byte[size];
} finally {
    $sync.unlock();
}

So taking the last sample of code:

Object o = new Object();            // Slot 0.
synchronized (o) {                  // Slot 1.
    byte[] data1 = new byte[size];  // Slot 2.
}                                 
int i = 0;                          // Slot 1.
synchronized (o) {                  // Slot 2. (clobbers data1, was slot 1)
    byte[] data2 = new byte[size];  // Slot 3.
}
Tom Hawtin - tackline
It's seems so.public static void main(String[] args) throws InterruptedException { synchronized (new Object()) { byte[] data1 = new byte[size]; } int i = 0; int k = 0; byte[] data2 = new byte[size];}Working very well. If you disassemble this code you will see that "k" variable overwrite stackframe of "data1".
dotsid
Speechless. Great stuff.
Robert Munteanu
A: 

Puzzles are interesting, but for the pragmatic programmer who does not want to think about (or more importantly depend on) the more arcane aspects of garbage collection, would setting data1 = null as soon as it is no longer needed solve the problem? If so, I'd rather do that then weird synchronized block and dummy variable magic.

Of course, it is sad that the memory does not get freed as soon as the array goes out of scope, which is what people were hoping for in this thread.

This should be fixed in the JVM.

Thilo
Of course, a pragmatic programmer is fine, until he/she causes a problem in production due to ignorance.
Tom Hawtin - tackline
Not sure if refusing to get entangled in details like this counts as ignorance. This seems like a rare edge-case that no one except a JVM specialist can be expected to know about.
Thilo
A: 

All this behaviour is implementation dependent. The garbage collector runs in its own asynchronous thread that has nothing to do with the synchronization behavior of your program. You simply do not know when the array referenced by data1 will be garbage collected -- you can only hope that it will happen in a "reasonable" amount of time after it goes out of scope/all references to it are gone.

If you are worried about running out of memory in your program, you can explicitly attempt to trigger a garbage collection cycle with System.gc(). But even this does not guarantee that enough memory will be available when you allocate data2. Calling System.gc() is simply a hint to the runtime that you'd like a garbage collection cycle now.

In Java, memory allocation and deallocation is non-deterministic. The garbage collector will run when it runs and you can't make it run at the program level. There's no relevant differences between the code snippets you posted, because gc behaviour is non-deterministic and the precise moment at which it is triggered is implementation and system dependent. Sometimes this is a problem for your application -- if it's an OS or runs in a memory constrained embedded device, for example -- and you will need to code in C++ or some other language where memory management is deterministic. For most of us, though, we just trust that the garbage collector will behave in a reasonably reasonable manner and that is good enough for most purposes -- although, as you see, you can create contrived code that causes problems.

Update: Embarrassing. As a couple of other commenters have reminded me, a garbage collection cycle is explicitly triggered before the jvm throws an OutOfMemory error. However, the behaviour is still non-deterministic: as this link explains, the jvm does not guarantee that all dead objects will be detected in one garbage collection cycle.

But before throwing an OutOfMemoryError, does not the JVM force a full (non-background) garbage collection if it has to?
Thilo