Java 6过多的内存使用

Java 6是否比大预期的应用程序消耗更多的内存?

我有一个我已经开发多年的应用程序,直到现在我的特定测试配置大约需要30-40 MB; 现在使用Java 6u10和11,它在活动时需要几百个。 它反弹很多,在50M到200M之间的任何地方,当它空转时,它会执行 GC并将内存放下。 此外,它还会产生数百万的页面错误。 所有这些都是通过Windows任务管理器观察到的。

因此,我在我的探查器(jProfiler)下使用jVisualVM运行它,并且它们都表明通常适度的堆和大约30M的perm-gen用法组合,即使在完全活动进行负载测试循环时也是如此。

所以我很神秘! 它不只是从Windows虚拟内存池请求更多内存 – 这显示为200M“内存使用”。

澄清:我想对此非常清楚 – 使用Java VisualVM在18小时内观察到类堆和perm gen堆已经非常稳定。 分配的易失性堆(eden和tenured)位于16MB(它在前几分钟内达到)不动,并且这个内存的使用波动在一个完美的模式,从8MB到16MB均匀增长,此时GC启动将它降回8MB。 在这18个小时的时间内,自从我进行压力测试以来,系统处于恒定的最大负载下。 在许多运行中都可以看到这种行为完美一致的可重复性。 唯一的exception现象是,当通过任务管理器观察从Windows获取的内存时,从64MB到900 + MB的所有地方都会出现波动。

更新2008-12-18:我用-Xms16M -Xmx16M运行程序没有任何明显的不利影响 – 性能很好,总运行时间大致相同。 但是短时间内的内存使用率仍然达到了180M左右。

更新2009-01-21:似乎答案可能是线程数 – 请参阅下面的答案。


编辑:我的意思是数以百万计的页面错误 – 在30M +的范围内。

编辑:我有一台4G机器,所以200M在这方面并不重要。

回应Ran对该答案的评论中的讨论,这是一个测试案例,certificateJVM在某些情况下会将内存释放回操作系统:

public class FreeTest { public static void main(String[] args) throws Exception { byte[][] blob = new byte[60][1024*1024]; for(int i=0; i 

在Java 1.4和Java 6 JVM(来自Sun)上,当计数达到40左右时,我看到JVM进程的大小减小了。

您甚至可以使用-XX:MaxHeapFreeRatio和-XX:MinHeapFreeRatio选项调整确切行为 - 该页面上的某些选项也可以帮助回答原始问题。

我不知道页面错误。 但是关于为Java分配的巨大内存:

  1. Sun的JVM 只分配内存,从不释放它(直到JVM死亡)只有在内部内存需求和分配内存之间的特定比率下降到(可调)值之后才释放内存。 JVM以-Xms中指定的数量开始,可以扩展到-Xmx中指定的数量。 我不确定默认值是什么。 每当JVM需要更多内存(新对象/基元/数组)时,它就会从操作系统中分配整个块。 然而,当需求消退时(暂时需要,也见2)它不会立即将内存释放回操作系统,而是在达到该比率之前保持自身。 我曾经被告知JRockit表现得更好,但我无法validation它。

  2. Sun的JVM基于多个触发器运行完整的GC。 其中一个是可用内存量 – 当它下降太多时,JVM会尝试执行完整的GC以释放更多内存。 因此,当从OS分配更多内存(瞬间需要)时,降低了完整GC的可能性。 这意味着虽然您可能会看到30Mb的“实时”对象,但可能会有更多“死”对象(无法访问),只是等待GC发生。 我知道你的套件有一个很棒的视图叫做“死对象”,你可以看到这些“遗留物”。

  3. 在“-server”模式下,Sun的JVM以并行模式运行GC(而不是旧的串行“停止世界”GC)。 这意味着虽然可能存在要收集的垃圾,但由于其他线程占用了所有可用的CPU时间,因此可能无法立即收集垃圾。 它将在到达内存之前收集(好吧,有点。请参阅http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html ),如果可以从操作系统分配更多内存,它可能会在GC运行之前。

结合起来,大型初始内存配置和短时间突发创建大量短期对象可能会创建一个所描述的场景。

编辑:将“never deallcoates”更改为“仅在达到比率后”。

过多的线程创建完美地解释了您的问题:

  • 每个线程都有自己的堆栈,它与堆内存分开,因此不会被分析器注册
  • 默认的线程堆栈大小非常大,IIRC 256KB(至少是Java 1.3 )
  • Tread堆栈内存可能不会被重用,因此如果您创建并销毁大量线程,则会出现大量页面错误

如果您确实需要拥有数百个线程,则可以通过-Xss命令行参数配置线程堆栈大小。

垃圾收集是一项相当神秘的科学。 随着最新技术的发展,未经调整的行为将随之发生变化。

Java 6具有不同的默认GC行为和与早期JVM版本不同的“人体工程学”。 如果你告诉它它可以使用更多的内存(在命令行中显式,或者由于未能指定更明确的内容而隐式),如果它认为这可能会提高性能,它将使用更多的内存。

在这种情况下,Java 6似乎相信保留堆可以增长的额外空间将提供更好的性能 – 可能是因为它认为这将导致更多对象在Eden空间中死亡,并限制提升到的对象数量终身发电空间。 根据硬件的规格,JVM认为这个额外的保留堆空间不会导致任何问题。 请注意,JVM在得出结论时所做的许多(尽管不是全部)假设都是基于“典型”应用程序,而不是您的特定应用程序。 它还根据您的硬件和操作系统配置文件进行假设。

如果JVM做出了错误的假设,你可以通过命令行影响它的行为,虽然很容易出错…

可以在此处找到有关Java 6中性能更改的信息。

在内存管理白皮书中讨论了内存管理和性能影响。

在过去的几周里,我有理由调查并纠正线程池对象(一个Java 6之前的multithreading执行池)的问题,其中启动了比所需更多的线程。 在有问题的工作中,最多可能有200个不必要的线程。 线程不断死亡,新的线程取而代之。

纠正了这个问题后,我想再次运行测试,现在看来内存消耗是稳定的(尽管比旧JVM高20左右)。

所以我的结论是内存中的峰值与运行的线程数(数百)有关。 不幸的是,我没有时间进行实验。

如果有人想尝试并用他们的结论回答这个问题,我会接受这个答案; 否则我会接受这个(等待2天后)。

此外,页面错误率也会下降(降低10倍)。

此外,线程池的修复程序纠正了一些争用问题。

升级到Java 6u10后,在Java堆之外分配了大量内存? 只能是一件事:

Java6 u10发行说明 : “新的Direct3D加速渲染管道(…)默认启用”

默认情况下,Sun在Java 6u10中启用了Direct 3D加速。 此选项会创建大量(临时?)本机内存缓冲区,这些缓冲区在Java堆外部分配。 添加以下vm参数以再次禁用它:

-Dsun.java2d.d3d = FALSE

请注意,这不会禁用2D硬件加速,只是一些可以利用3D硬件加速的function。 您将看到您的Java堆使用量将增加多达7MB,但这是一个很好的权衡,因为您将节省约100MB(+)的临时易失性内存。

我在两个平台上的2个Swing桌面应用程序中进行了大量的测试:

  • 配备nVidia GTX 260显卡的高端Intel-i7,
  • 一款配备英特尔显卡的3年笔记本电脑。

在这两个硬件平台上,该选项几乎没有主观差异。 (测试包括:滚动表,缩放图形流程图,图表等)。 在少数测试中,某些东西略有不同,禁用d3d可以直观地提高性能。 我怀疑内存管理/带宽问题抵消了d3d加速function应该实现的任何好处。 (你的旅费可能会改变!)

如果您需要进行一些性能调整,这里有一个很好的参考 (例如“Java 2D故障排除”)

你在使用ConcMarkSweepcollections家吗? 由于内存碎片增加,它可能会增加应用程序所需的内存量,以及“浮动垃圾” – 只有在收集器检查后才能访问的对象,因此直到下一次传递才会收集。