Reputation: 9955
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];
}
Upvotes: 3
Views: 600
Reputation: 3644
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.
Upvotes: 0
Reputation: 147164
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.
}
Upvotes: 6
Reputation: 262794
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.
Upvotes: 0
Reputation: 29360
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];
Upvotes: -1