Java -Xmx在大量RAM上的怪异行为

您可以使用-Xmx选项来控制java中的最大堆大小。

使用此开关,我们正在Windows上遇到一些奇怪的行为。 我们运行一些非常健壮的服务器(想想196gb ram)。 Windows版本是Windows Server 2008R2

Java版本是1.6.0_18,64位(显然)。

无论如何,即使进程使用的内存比-Xmx设置的内存less得多,我们仍然有一些怪异的进程正在退出内存exception的错误。

所以我们写了一个简单的程序,每次按下回车键就会分配一个1GB的字节数组,并将字节数组初始化为随机值(以防止任何内存压缩等)。

基本上,如果我们使用-Xmx35000m(大约35 GB)运行程序,那么当我们打开25 GB的进程空间(使用Windows任务pipe理器进行测量)时,会发生内存不足的情况。 我们在分配了24GB的1GB的块(BTW)之后就打了这个,这样就可以检查出来了。

只需为-Xmx选项指定一个较大的值,就可以使程序正常工作以适应大量的内存。

那么发生了什么? -Xmx只是“closures”。 顺便说一句:我们需要指定-Xmx55000m来获得一个35 GB的进程空间…

任何想法正在发生什么?

他们是在Windows JVM中的错误?

即使在-Xmx选项和进程明智之间存在断开连接的情况下,将-Xmx选项设置得更大是否安全?

理论#1

当您使用-Xmx35000m请求一个35Gb堆时,实际上您所说的是允许用于堆的总空间为35Gb。 但总空间由Tenured Object空间(对于经历多个GC周期的对象),新创建对象的Eden空间以及垃圾收集期间将被复制到的其他空间组成。

问题是一些空间不是也不能用于分配新的对象。 所以实际上,你“失去”35Gb的大部分开销。

有各种各样的-XX选项可以用来调整各个空间的大小,等等。你可以尝试摆弄他们,看看他们是否有所作为。 有关更多信息,请参阅此文档 。 (第8节列出了常用的GC调优选项。-XX:NewSpace选项看起来很有前景…)


理论#2

这可能是因为你正在分配巨大的对象。 IIRC,可以将大于一定大小的对象直接分配到Tenured Object空间。 在你的(高度人造的)基准测试中,这可能会导致JVM不把东西放入Eden空间,因此能够使用比正常情况更少的堆空间。

作为一个实验,尝试改变你的基准来分配大量的小对象,看看在OOME之前是否能够使用更多的可用空间。


以下是我会打折的其他一些理论:

  • “你遇到了OS限制。” 我会打折这个,因为你说你可以通过增加-Xmx …设置来获得更大的内存利用率。

  • “Windows任务管理器正在报告虚假号码。” 我会打折,因为报告的数字大概与您认为您的应用程序已分配的25Gb相匹配。

  • “你正在失去其他东西的空间,比如堆积如山的堆积物。” AFAIK,permgen堆大小是控制和独立的“正常”堆积帐。 其他非堆内存使用情况是一个常数(为应用程序)或依赖于应用程序做特定的事情。

  • “你正在遭受碎片堆积。” 所有的JVM垃圾收集器都是“复制收集器”,这个收集器家族拥有堆节点自动压缩的属性。

  • “Windows上的JVM错误” 不大可能。 Windows安装中必须有成千上万的64位Java才能最大化堆大小。 别人会注意到…


最后,如果你不这样做,因为你的应用程序需要你分配大量的内存,并“永远”挂在它上面……你很有可能追逐阴影。 一个“正常的”大内存应用程序不会做这种事情,而JVM被调整为正常的应用程序…而不是异常的。

而如果你的应用程序确实有这样的行为,那么务实的解决方案就是将-Xmx …选项设置得更大一些,只要你开始遇到操作系统级别的问题。

要感觉到你正在测量什么,你应该使用一些不同的工具:

  1. Windows任务管理器(我只知道Windows XP,但是我听说任务管理器已经改进了。
  2. Sysinternals的procexpvmmap
  3. 来自JVM的jconsole (您正在使用Sun Oracle HotSpot JVM,是不是?)

现在您应该回答以下问题:

  • jconsole对使用的堆大小有什么看法? 这与procexp什么不同?
  • 如果使用非零数字填充字节数组,而不是将它们保持为0那么procexp的值是否procexp发生变化?

你尝试打开GC的详细输出来找出最后一次分配失败的原因。 是因为操作系统未能为本地JVM进程分配超过25GB的堆,还是因为GC正在对其可管理的最大内存进行某种限制。 我建议你也使用jconsole连接到命令行进程,以查看分配失败之前堆的状态。 像sysinternals进程管理器这样的工具可能会提供更好的细节,因为如果它在jvm进程中发生故障。

由于这个过程是25GB的死亡,你有一个世代收藏家,也许其余的几代消费10GB。 我建议您安装JDK 1.6_u24,并使用jvisualvm和visualGC插件来查看GC正在做什么,特别是考虑到所有代码的大小,以了解35GB堆是如何由GC / VM内存切入不同的区域经理。

如果您不熟悉Generational GC,请参阅此链接http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html#generation_sizing.total_heap

我认为这与分割堆有关。 空闲内存可能不是单个连续的空闲区域,当您尝试分配一个大块时,这会失败,因为请求的内存不能分配到一个单独的块中。

由Windows任务管理器显示的内存是分配给进程的总内存,包括代码,堆栈,perm gen和堆的内存。 您使用点击程序测量的内存是jvm提供给运行jvm程序的堆的数量。 在本质上,Windows分配给JVM的内存总量应该比JVM作为堆内存提供给程序的大。