Java 6过多的内存使用

时间:2008-12-14 15:34:26

标签: java memory-management

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

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

所以,我在我的探查器(jProfiler)下运行它并使用jVisualVM,它们都表明通常适度的堆和大约30M的用户使用率,即使完全处于负载测试周期时也是如此。 / p>

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

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

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

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


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

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

7 个答案:

答案 0 :(得分:13)

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

public class FreeTest
{
    public static void main(String[] args) throws Exception
    {
        byte[][] blob = new byte[60][1024*1024];
        for(int i=0; i<blob.length; i++)
        {
            Thread.sleep(500);
            System.out.println("freeing block "+i);
            blob[i] = null;
            System.gc();
        }
    }
}

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

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

答案 1 :(得分:9)

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

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

  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运行之前。

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

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

答案 2 :(得分:7)

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

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

如果你真的需要有数百个线程,可以通过-Xss命令行参数配置线程堆栈大小。

答案 3 :(得分:4)

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

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

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

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

可以找到有关Java 6中性能变化的信息here

Memory Management White Paper中讨论了内存管理和性能影响。

答案 4 :(得分:3)

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

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

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

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

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

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

答案 5 :(得分:1)

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

Java6 u10 Release Notes“新的Direct3D加速渲染管道(...)默认启用”

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

-Dsun.java2d.d3d = false

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

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

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

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

如果你需要进行一些性能调整,这里是excellent reference(例如“疑难解答Java 2D”)

答案 6 :(得分:0)

您使用的是ConcMarkSweep收藏家吗?由于内存碎片增加,它可能会增加应用程序所需的内存量,以及“浮动垃圾” - 只有在收集器检查后才会无法访问的对象,因此在下一次传递之前不会收集这些内容。