OutOfMemoryError on overprovisioned heap

本文探讨了Java应用程序遭遇内存溢出错误的原因,特别是在分配的数据结构理论上应能适配提供的JVM堆大小的情况下。通过一个具体示例,揭示了如何通过调整JVM参数来解决这一问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

OutOfMemoryError on overprovisioned heap

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:+PrintGCDetails:

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)

Heap
 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.

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:

Heap
 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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值