Splunk Completes Acquisition of Plumbr Learn more

To blog |

OutOfMemoryError on overprovisioned heap

May 15, 2014 by Vladimir Šor Filed under: Memory Leaks

Why am I getting the OutOfMemoryError when allocating a data structure that should happily fit within the heap I have provided for the JVM? This was a question I recently faced.

Indeed, when looking at what the developer was trying to accomplish and triple-checking the heap size given to the JVM via the -Xmx parameter, it indeed seemed that something shady was going on.

30 minutes later we understood the situation and solved the mystery.  But it was indeed not obvious at the first place, so I thought it might save someone a day if I described the underlying problem in more details.

As always, the best way to understand a problem is via a hands-on example. I have constructed a small synthetic test case:

package eu.plumbr.demo;
class ArraySize {
	public static void main(String... args) {
		int[] array = new int[1024*1024*1024];

The code is simple – all it tries to do is to allocate an array with one billion elements.  Now, considering that java int primitives require 4 bytes, one might think that running the code with 6g heap would run just fine. After all, those billion integers should consume only 4g memory. So why do I see the following when I execute the code?

My Precious:bin me$ java –Xms6g –Xmx6g eu.plumbr.demo.ArraySize
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 	at eu.plumbr.demo.ArraySize.main(ArraySize.java:6)

Before just tossing in even more heap (as a matter of fact, with –Xmx7g the example above runs just fine), let us try to understand why our expectation was wrong.

First – the int primitives in java do indeed require 4 bytes.  So it is not like our JVM implementation has gone crazy over night.  And I can assure you that the math is also correct – 1024*1024*1024 int primitives indeed would require 4,294,967,296 bytes or 4 gigabytes.

To understand what is happening, lets run the very same case and turn on garbage collection logging by specifying -XX:+PrintGCDetailsls :

My Precious:bin me$ java –Xms6g -Xmx6g -XX:+PrintGCDetails eu.plumbr.demo.ArraySize

-- cut for brevity --

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at eu.plumbr.demo.ArraySize.main(ArraySize.java:6)

 PSYoungGen      total 1835008K, used 125829K [0x0000000780000000, 0x0000000800000000, 0x0000000800000000)
  eden space 1572864K, 8% used [0x0000000780000000,0x0000000787ae15a8,0x00000007e0000000)
  from space 262144K, 0% used [0x00000007e0000000,0x00000007e0000000,0x00000007f0000000)
  to   space 262144K, 0% used [0x00000007f0000000,0x00000007f0000000,0x0000000800000000)
 ParOldGen       total 4194304K, used 229K [0x0000000680000000, 0x0000000780000000, 0x0000000780000000)
  object space 4194304K, 0% used [0x0000000680000000,0x0000000680039608,0x0000000780000000)
 PSPermGen       total 21504K, used 2589K [0x000000067ae00000, 0x000000067c300000, 0x0000000680000000)
  object space 21504K, 12% used [0x000000067ae00000,0x000000067b087668,0x000000067c300000)

The answers are now staring right into our eyes: even though we have plenty of total heap available, no individual area in the heap is large enough to hold 4g of objects. Our 6g heap is divided into four separate regions, sized like this:

  • Eden 1,536M
  • Survivor spaces (from and to) 256M each
  • OldGen 4,096M

Now, bearing in mind that object allocations must fit into a single region we indeed can see that the application stands no chance – there is just not enough room in any of our heap regions to accommodate this single 4g allocation.

Did you know that 20% of Java applications have memory leaks? Don’t kill your application – instead find and fix leaks with Plumbr in minutes.

So – is our only hope now to increase heap further? Even if we already have over-provisioned by nearly 50% – handing 6g of heap to a data structure which should fit into 4g? Not so fast – there is an alternative solution available. You can set the size of the different areas in memory. It is not as straightforward and user-friendly as one might expect, but two small modifications of the startup configuration will do the trick. When launching the same code with just two extra options:

My Precious:bin me$ java -Xms6g -Xmx6g -XX:NewSize=5g -XX:SurvivorRatio=10 eu.plumbr.demo.ArraySize

then the program does its job and no OutOfMemoryError is being thrown. Adding -XX:+PrintGCDetails to the startup also explains it:

 PSYoungGen      total 4806144K, used 4369080K [0x00000006c0000000, 0x0000000800000000, 0x0000000800000000)
  eden space 4369408K, 99% used [0x00000006c0000000,0x00000007caaae228,0x00000007cab00000)
  from space 436736K, 0% used [0x00000007e5580000,0x00000007e5580000,0x0000000800000000)
  to   space 436736K, 0% used [0x00000007cab00000,0x00000007cab00000,0x00000007e5580000)
 ParOldGen       total 1048576K, used 0K [0x0000000680000000, 0x00000006c0000000, 0x00000006c0000000)
  object space 1048576K, 0% used [0x0000000680000000,0x0000000680000000,0x00000006c0000000)
 PSPermGen       total 21504K, used 2563K [0x000000067ae00000, 0x000000067c300000, 0x0000000680000000)
  object space 21504K, 11% used [0x000000067ae00000,0x000000067b080c90,0x000000067c300000)

We see that the sizes of regions are now indeed what we asked for:

  • Young size int total (eden + two survivor spaces) is 5g, as specified by our -XX:NewSize=5g parameter
  • eden is 10x larger than survivor, as we specified with the -XX:SurvivorRatio=10 parameter.

Note that in our case, both of the parameters were necessary. Specifying just the  -XX:NewSize=5g would still split it between eden and survivors in a way where no individual area can hold the required 4g.

Hopefully reading this explanation will save you a day of debugging in the future. Or help you avoid over-provisioning the resources. If you found the tip useful, I can only recommend subscribing to our Twitter or RSS feeds – we keep experimenting and sharing our knowledge.



As recently instancied objects which doesn’t fit in young generation are stored in old generation, an alternative could have been to set up a bigger old generation :
-Xmx5g -XX:NewRatio=6 -XX:+PrintGCDetails

Minor collections are meant to be as short as possible as the application pause its execution until it’s done. So, the young generation should be relatively low sized.


In a real program, would this still not soon run into problems when the GC tries to move the array out of Eden into OldGen, which is not only about 1G in size?

It seems you would need to set the total heap size to over 8G: 4G in Eden, 4G in OldGen and some for Survivor.

Troy Daniels