Reputation: 1399
I'm currently working on a game with infinite terrain, so I am frequently creating new arrays for each chunk. The problem I ran into was that even after I removed all references of a chunk, it appeared that the memory it used was not freed. I checked the memory usage with task manager; however, I also used VisualVM and it wasn't showing any signs of there being a memory problem (the amount of bytes allocated remained consistent). Anyways, I managed to simplify the problem to this trivial code:
import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class Leaker
{
public static void main(String[] args)
{
new Leaker().run();
}
public void run()
{
JFrame frame = new JFrame();
frame.setPreferredSize(new Dimension(100, 100));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.addKeyListener(new KeyListener()
{
@Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_1)
{
double[] leak = new double[9999999];
System.out.println("*leaking intensifies*");
}
if (e.getKeyCode() == KeyEvent.VK_2)
{
System.gc();
System.out.println("garbage collected");
}
}
@Override
public void keyTyped(KeyEvent e)
{
}
@Override
public void keyReleased(KeyEvent e)
{
}
});
}
}
What I thought should happen is that when I press 1, a new double array would be allocated, but since it's only being allocated on the stack, once the method returns, the memory would be freed shortly after. However, this is not the case. Task manager shows a growing amount of memory being used for this application each time I press 1. Even forcing garbage collection has no effect. So, what am I missing here?
If for some reason you don't believe me, simply copy and paste it.
Upvotes: 0
Views: 79
Reputation: 133
Where did u write -XMX256M? Is there a location where u can make this setting application specific? Or did u just write it in general java settings, because then this limit is used for ANY java app u are running.
Upvotes: 0
Reputation: 1399
So, my leaking problem turned out to not be a leaking problem at all. To test out what Stephen C explained, I used Streak324's and TizianoPiccardi's suggestion of setting the heap memory limit when the memory from the heap will actually be given back to the OS via -XMX256M. My game now never goes over that limit. In fact, if I had just ran my game long enough, it would have never went over 1024MB, but I never tried it. Anyways, thank you everyone.
Upvotes: 0
Reputation: 718678
What I thought should happen is that when I press 1, a new double array would be allocated, but since it's only being allocated on the stack, once the method returns, the memory would be freed shortly after.
That is not correct.
The double array is allocated on the heap. All objects are allocated on the heap1.
The reference to the object that is held in a local variable which will be on the stack. By the time that the keyPressed()
method returns, the local will have gone away, making the double array unreachable. But it won't actually be reclaimed until the GC gets around to it.
Under normal circumstances, the JVM only runs the GC when it detects that heap space is running out. (The actual triggering condition depends on the kind of GC that you are using ...) You are trying to force the issue by calling System.gc()
, and in your case2 this will be triggering a GC.
So ... the GC runs, and reclaims the space. How come you are not seeing this? Well, you don't say how you are monitoring the JVM's memory usage, but I expect that you are using OS level tools (e.g. top, or the windows task monitors.) These tools won't see a drop in the process memory usage when the GC reclaims the space. That is because the GC does not normally "give back" the reclaimed space to the operating system. Rather, it keeps the space ready for the next time the application allocates a big object / array. (This makes Java memory management simpler and more efficient.)
A JVM can be configured to give back unneeded memory, via (yet another) JVM switch, but even that mechanism is reluctant. It takes a few GC cycles before the JVM will decide that it has more heap memory than it needs and give some back.
In summary: What you have here is not a "memory leak". Rather it is evidence that Java does not reclaim space immediately, and that it is reluctant to "give back" any reclaimed heap space to the operating system. It certainly won't give the memory back immediately.
1 - There is JVM switch to enable something known as "escape analysis" in the JIT compiler. This will cause it to try to identify cases where an object is allocated that cannot "escape" the local method call, and can therefore be allocated on the stack. However, I don't think it applies to arrays, and it certainly wouldn't apply to this array ... because the array is too big to allocate on a thread stack.
2 - You will see references all over that place that calling System.gc()
does not guarantee that the GC runs. This is true ... the javadocs say so. However, in practice System.gc()
will run the GC unless the JVM was launched with a flag that says "ignore System.gc()" calls.
Upvotes: 2
Reputation: 153
@TizianoPiccardi is right. If you do -Xmx128M as a command argument on your program, the amount of memory used shouldn't go above that 128MB limit, and would caused unused memory to be cleaned when the total memory reaches that limit. If your using eclipse, type the argument in Run Configurations -> Arguments -> VM Arguments
Upvotes: 1
Reputation: 1915
An array is an object and all objects are allocated on the heap. The local reference to the array you created is on the stack.
System.gc()
is just a suggestion to the Java VM that garbage collection should be run but it's not guaranteed to run.
You can use the Visual GC
plugin of visualvm
to see when gc happens. For me Eden space was cleaned when I pressed 2 of your code (or when I press 1 until Eden space is full).
In summary I see no leak here.
Upvotes: 0