views:

2327

answers:

6

Does Java 6 consume more memory than you expect for largish applications?

I have an application I have been developing for years, which has, until now taken about 30-40 MB in my particular test configuration; now with Java 6u10 and 11 it is taking several hundred while active. It bounces around a lot, anywhere between 50M and 200M, and when it idles, it does GC and drop the memory right down. In addition it generates millions of page faults. All of this is observed via Windows Task Manager.

So, I ran it up under my profiler (jProfiler) and using jVisualVM, and both of them indicate the usual moderate heap and perm-gen usages of around 30M combined, even when fully active doing my load-test cycle.

So I am mystified! And it not just requesting more memory from the Windows Virtual Memory pool - this is showing up as 200M "Mem Usage".

CLARIFICATION: I want to be perfectly clear on this - observed over an 18 hour period with Java VisualVM the class heap and perm gen heap have been perfectly stable. The allocated volatile heap (eden and tenured) sits unmoved at 16MB (which it reaches in the first few minutes), and the use of this memory fluctuates in a perfect pattern of growing evenly from 8MB to 16MB, at which point GC kicks in an drops it back to 8MB. Over this 18 hour period, the system was under constant maximum load since I was running a stress test. This behavior is perfectly and consistently reproducible, seen over numerous runs. The only anomaly is that while this is going on the memory taken from Windows, observed via Task Manager, fluctuates all over the place from 64MB up to 900+MB.

UPDATE 2008-12-18: I have run the program with -Xms16M -Xmx16M without any apparent adverse affect - performance is fine, total run time is about the same. But memory use in a short run still peaked at about 180M.

Update 2009-01-21: It seems the answer may be in the number of threads - see my answer below.


EDIT: And I mean millions of page faults literally - in the region of 30M+.

EDIT: I have a 4G machine, so the 200M is not significant in that regard.

+6  A: 

I don't know about the page faults. but about the huge memory allocated for Java:

  1. Sun's JVM only allocates memory, never deallocates it (until JVM death) deallocates memory only after a specific ratio between internal memory needs and allocated memory drops beneath a (tunable) value. The JVM starts with the amount specified in -Xms and can be extended up to the amount specified in -Xmx. I'm not sure what the defaults are. Whenever the JVM needs more memory (new objects / primitives / arrays) it allocates an entire chunk from the OS. However, when the need subsides (a momentary need, see 2 as well) it doesn't deallocates the memory back the the OS immediately, but keeps it to itself until that ratio has been reached. I was once told that JRockit behaves better, but I can't verify it.

  2. Sun's JVM runs a full GC based on several triggers. One of them is the amount of available memory - when it falls down too much the JVM tries to perform a full GC to free some more. So, when more memory is allocated from the OS (momentary need) the chance for a full GC is lowered. This means that while you may see 30Mb of "live" objects, there might be a lot more "dead" objects (not reachable), just waiting for a GC to happen. I know yourkit has a great view called "dead objects" where you may see these "left-overs".

  3. In "-server" mode, Sun's JVM runs GC in parallel mode (as opposed the older serial "stop the world" GC). This means that while there may be garbage to collect, it might not be collected immediately because of other threads taking all available CPU time. It will be collected before reaching out of memory (well, kinda. see http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html), if more memory can be allocated from the OS, it might be before the GC runs.

Combined, a large initial memory configuration and short bursts creating a lot of short-lived objects might create a scenario as described.

edit: changed "never deallcoates" to "only after ratio reached".

Ran Biron
Except... both jProfiler and Java VisualVM, as stated, show the combined heap and perm-gen stable at 30M, and the heap is fluctuating between 8M and 16M; so it's not my objects doing it.
Software Monkey
Point 1 is definitely incorrect. The JVM does deallocate memory, it's just pretty reluctant in doing it. And I really doubt 3 as well, since servers generally are all about throughput, and "stop the world" GC have a better overall throughput.
Michael Borgwardt
Point 1: I believe this changed in Java 6. Java 5 did not deallocate (from memory)
Stephen
Yes it did. I once wrote a program to test that point, and I even think it was on 1.4. It took some time and a LOT of freed memory, but eventually, the JVM started reducing its process space.
Michael Borgwardt
I never, in my whole life as a developer, saw the JVM release memory back to the OS. On the contrary, I saw several articles (none from Sun though) that stated the opposite, and this is what I cited (without references). If you have a concrete test case to prove me wrong, I'd be glad to see it.
Ran Biron
I stand corrected. However, during my tests the freeing began only at block 50 and went in a very slow pace - e.g. on block 55 (only 5 blocks allocated!) I still had 30mb of memory taken from the OS. So I argue that in a practical run I'm still correct.
Ran Biron
As I said, the JVM *is* pretty reluctant in deallocating memory - though I observed it starting at block 40, so it might depend on other factors as well, possibly the total system memory.
Michael Borgwardt
@Ran: I would buy point (2) if the *observed* heap use was not stable at around 30MB (16 eden, about 14 tenured+perm), clearing showing my objects growing to 16MB, GC dropping them to about 8, and growning again, in a 100% consistent pattern. Simply put, the JVM doesn't need the mem for my objects!
Software Monkey
@Ran: See my update - it runs fine with a 16MB heap.
Software Monkey
+7  A: 

In response to a discussion in the comments to Ran's answer, here's a test case that proves that the JVM will release memory back to the OS under certain circumstances:

public class FreeTest
{
    public static void main(String[] args) throws Exception
    {
        byte[][] blob = new byte[60][1024*1024];
        for(int i=0; i<blob.length; i++)
        {
            Thread.sleep(500);
            System.out.println("freeing block "+i);
            blob[i] = null;
            System.gc();
        }
    }
}

I see the JVM process' size decrease when the count reaches around 40, on both Java 1.4 and Java 6 JVMs (from Sun).

You can even tune the exact behaviour with the -XX:MaxHeapFreeRatio and -XX:MinHeapFreeRatio options -- some of the options on that page may also help with answering the original question.

Michael Borgwardt
seen, tested, seem my response on my own answer trunk.
Ran Biron
+3  A: 

Garbage collection is a rather arcane science. As the state of the art develops, un-tuned behaviour will change in response.

Java 6 has different default GC behaviour and different "ergonomics" to earlier JVM versions. If you tell it that it can use more memory (either explicitly on the command line, or implicitly by failing to specify anything more explicit), it will use more memory if it believes that this is likely to improve performance.

In this case, Java 6 appears to believe that reserving the extra space which the heap could grow into will give it better performance - presumably because it believes that this will cause more objects to die in Eden space, and limit the number of objects promoted to the tenured generation space. And from the specifications of your hardware, the JVM doesn't think that this extra reserved heap space will cause any problems. Note that many (though not all) of the assumptions the JVM makes in reaching its conclusion are based on "typical" applications, rather than your specific application. It also makes assumptions based on your hardware and OS profile.

If the JVM has made the wrong assumptions, you can influence its behaviour through the command line, though it is easy to get things wrong...

Information about performance changes in java 6 can be found here.

There is a discussion about memory management and performance implications in the Memory Management White Paper.

Bill Michell
I tend to agree that this is most likely a GC tuning issue. The GC functionality has changed markedly over each iteration from 1.4 -> 6. There are a number of articles discussing GC tuning. It's worth it to look them up and go through them to see if it would help your issue.
Spencer K
But if GC was the issue, I would expect the profiler to show the heap growing - it doesn't. The heap is completely stable at 16MB allocated, fluctuating between 8 MB and 16 MB in use.
Software Monkey
I'm not saying that the JVM *needs* the extra space. I'm saying that it believes that having the extra space will, on balance of probablilities, give better performance in a typical application. If you know that your application is not typical, you can tune the GC system. Read up on GC ergonomics.
Bill Michell
Granted... but when it's currently using 30MB supposing it's guessing that it might need 800MB smells like it's not for the object heap that this memory is being. Meaning writing this off as "Java thinks it needs it for your objects" doesn't seem to fit.
Software Monkey
PS: I have not only read exhaustively on the theory of GC, and specifically on every GC engine for every Java release, but I have *observed* the GC behavior of this specific app in this specific context - and it ain't GC using the memory!
Software Monkey
You misunderstand. The JVM, on the balance of probablilities, thinks that keeping this memory will be better than not keeping this memory. Based on your hardware, it makes the assumption that you aren't going to miss it. If it is wrong in its guesses, you can tell it explicitly what you want.
Bill Michell
A: 

Are you using the ConcMarkSweep collector? It can increase the amount of memory required for your application due to increased memory fragmentation, and "floating garbage" - objects that become unreachable only after the collector has examined them, and therefore are not collected until the next pass.

sk
I am using whatever the default GC is for the Windows client JVM (command line is just javaw -jar xxx).
Software Monkey
But if GC was the issue, I would expect the profiler to show the heap growing - it doesn't. The heap is completely stable at 16MB allocated, fluctuating between 8 MB and 16 MB in use.
Software Monkey
Are you using the -server or the -client JVM? Type "java -version" if you don't know the answer. The default GC is different in each case.
Bill Michell
@Bill - I guess I wasn't clear - By "The Windows client JVM" I meant javaw without additional switches (which is -client by default).
Software Monkey
+3  A: 

Over the last few weeks I had cause to investigate and correct a problem with a thread pooling object (a pre-Java 6 multi-threaded execution pool), where is was launching far more threads than required. In the jobs in question there could be up to 200 unnecessary threads. And the threads were continually dying and new ones replacing them.

Having corrected that problem, I thought to run a test again, and now it seems the memory consumption is stable (though 20 or so MB higher than with older JVMs).

So my conclusion is that the spikes in memory were related to the number of threads running (several hundred). Unfortunately I don't have time to experiment.

If someone would like to experiment and answer this with their conclusions, I will accept that answer; otherwise I will accept this one (after the 2 day waiting period).

Also, the page fault rate is way down (by a factor of 10).

Also, the fixes to the thread pool corrected some contention issues.

Software Monkey
+5  A: 

Excessive thread creation explains your problem perfectly:

  • Each Thread gets its own stack, which is separate from heap memory and therefore not registered by profilers
  • The default thread stack size is quite large, IIRC 256KB (at least it was for Java 1.3)
  • Tread stack memory is probably not reused, so if you create and destroy lots of threads, you'll get lots of page faults

If you ever really need to have hundreds of threads aound, the thread stack size can be configured via the -Xss command line parameter.

Michael Borgwardt