13 August 2009

Java Memory Usage

Dear Lazyweb,

Suppose I am writing a large server, and my implementation language is Java. The memory requirements for the server are larger than what the Sun JVM provides by default. So, I need the configure the JVM to use more memory.

This isn't rocket science. Everybody knows about Sun's "-Xmx" flag.

Here is Sun's documentation for the -Xmx flag:

-Xmxn
Specify the maximum size, in bytes, of the memory allocation pool. This value must a multiple of 1024 greater than 2MB. Append the letter k or K to indicate kilobytes, or m or M to indicate megabytes. The default value is 64MB. The upper limit .... [elided since the limits are a lot higher nowadays....] Examples:

-Xmx83886080
-Xmx81920k
-Xmx80m

There are a couple of things that confuse me about this flag. First of all, take a look at what runs just fine on my server box:

$ java -Xmx1000g HelloWorld
Hello, world!

I can assure you that my server box does not have 1000GB of any sort of memory!

More confusingly, here is another test I can run:

$ java -Xmx1000g SomeLongRunningProgram &
$ java -Xmx1000g SomeLongRunningProgram &

So, now I've got two programs running on my server box that seem to expect that they will, at some point in the program's runtime, be able to allocate 1000GB each for the memory pool. In my mind, this means that not only do I not have enough memory here, but the memory that I do have on this machine is heavily oversubscribed.

This whole situation confuses the heck out of me. My background includes quite a bit of embedded systems programming, and in the embedded world systems typically allocate their memory up front and then treat this memory as if it was a precious resource. If you can't allocate the memory that you need up front, you know something is wrong right away and it needs to be fixed. You don't get this behavior with the Sun JVM "-Xmx" flag.

OK, so, given the work that I have at hand, the "-Xmx" flag does not give me the behavior that I wanted. So, I looked into this a little bit more and thought about this problem. Soon, I was focusing my attention on the "-Xms" flag. Here's the documentation for this:

-Xmsn Specifies the initial size of the memory allocation
pool. This value must be a multiple of 1024
greater than 1 MB. Append the letter k or K to
indicate kilobytes or the letter m or M to indicate
megabytes. The default value is 2MB. Examples:

-Xms6291456
-Xms6144k
-Xms6m

So, after thinking about this a little bit, I eventually arrived at the following pattern for specifying memory allocation for server applications:

java -Xms1024m -Xmx1024m MyServer

The point is, I am specifying the same values for "-Xms" and "-Xmx". This pattern ensures that the JVM tries to allocate the memory that it needs up front. It isn't hard for me to experiment with my server machine and to learn how much memory I can allocate. If I allocate too much, I know about the problem right away. My server's memory never gets "oversubscribed" either.

So, I think that this pattern of specifying the same values for both "-Xms" and "-Xmx" is a good pattern for Java-based server applications. In fact, after a few minutes of searching, I came across this page, which seems to offer the same advice:

Setting -Xms and -Xmx to the same value increases predictability by removing the most important sizing decision from the JVM.

So, my question is this: under what circumstances would it be advantageous to specify different values for "-Xms" and "-Xmx"? I don't see a lot of upside for doing this, especially since I know that writing solid code that handles out-of-memory errors is a pretty difficult thing to do.

1 comment:

Brian St. Pierre said...

I can spell "java" on good days, and that's the limit of my jvaa knowledge...

But perhaps a use for having the initial pool smaller than the max pool is if you are going to start up many instances, and most won't use much, but you want more headroom for instances that do need more memory.

This presupposes that you have some higher level mechanism for limiting the number of processes so you don't overcommit. (Or that your overcommitment makes sense statistically.)

Not sure if this actually makes sense in java-land, though.