4 May 2006 - 16:00JVM vs CLR memory allocation
The Common Language Runtime (CLR - virtual machine for .NET) and the Java Virtual Machine (JVM) share similar architectures, but there are lots of differences, especially when it comes to memory management and allocation. Both virtual machines have automatic memory management in the form of garbage collection - the programmer is not normally responsible for allocating and releasing memory. Both virtual machines implement multiple generations of garbage collection for performance reasons, and both manage a “heap” of memory internally for application code use. However, there are a few subtle differences that don’t seem to be discussed much so I’m going to highlight them here. A caveat: what I’m going to discuss would probably be considered implementation details of Microsoft’s CLR and Sun’s JVM on the Windows platform. The behavior may be different in future releases, on other platforms, or on VM implementations from other vendors.
Difference #1: Fixed upper limit on the heap size
Both the CLR and the JVM manage an internal heap of memory that is used for allocations. Both VMs will grow the heap by allocating more memory from the operating system when needed. However, the JVM places a fixed upper limit on the heap size. This limit is specified by using the -Xmx switch when starting the runtime. If the JVM tries to satisfy an allocation that would result in the heap growing beyond that limit, and no garbage can be collected, then an OutOfMemoryError is thrown and the allocation fails. As far as I can tell, the CLR has no such artificial upper limit on the heap size. The CLR heap maximum size will be dependent on how much memory can be allocated from the operating system.
Difference #2: Releasing allocated memory
Something that surprises a lot of programmers about the JVM is that it does not release allocated memory back to the operating system, even if it could. For a hypothetical example, imagine that a JVM process starts and allocates 25 MB of memory from the operating system initially. Application code then attempts allocations that require an additional 50MB of heap. The JVM will allocate an additional 50MB from the operating system. Once the application code has stopped using that memory, it is garbage collected, and the JVM heap size will decrease. However, the JVM will not free the allocated operating system memory. For the rest of the lifetime of the process, that memory will remain allocated, even if the heap is never grown again.
In practice, this doesn’t tend to matter too much. Normally this “unused” memory will be paged out by the operating system so it doesn’t tend to impact other processes.
The CLR, on the other hand, will release allocated memory back to the operating system if it is no longer needed. In the example above, the CLR would have released the memory once the heap had decreased.
Sometimes graphs can illustrate concepts much better, so to illustrate this point further I made some graphs. I wrote roughly equivalent C# and Java programs. The program attempts a series of object allocations, each one larger than the last. After each allocation in the series, the program drops all references to the allocated objects and forces a GC to happen. The graphs show the private bytes and working set (process memory usage from the OS point of view) and heap size (heap memory usage from the JVM or CLR point of view).
The Java program:

And the C# program:

Remember that these graphs are showing equivalent programs. In each graph you can read “private bytes” as the amount of memory the process has allocated from the operating system. The “working set” shows the portion of the memory pages in physical RAM. Note that the JVM will never release memory back to the OS even if its internal heap decreases. For the CLR real memory usage follows the heap size. These graphs also show an example of the fixed heap size difference: in the JVM graph, the program encounters an OutOfMemoryError at the end of the graph (this is where the heap size levels off).
For each graph, I used performance counters to capture the private bytes and working set data. For the JVM graph, I used JSE 5 JMX memory monitoring (MemoryMXBean) to get the heap size. For the CLR graph, I tried using the CLR performance counters to capture the heap size, but it didn’t work for me. I ended up using GC.GetTotalMemory() which worked fine.
No Comments | Tags: Uncategorized