Java 6过多的内存使用

Law*_*Dol 22 java memory-management

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

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

因此,我在我的探查器(jProfiler)下运行它并使用jVisualVM,它们都指示通常适度的堆和大约30M的用户使用,即使完全处于我的负载测试周期.

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

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

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

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


编辑:我的意思是数百万页错误 - 在30M +区域.

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

Mic*_*rdt 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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


Ran*_*ron 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运行之前.

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

编辑:将"never deallcoates"更改为"仅在达到比率后".


Mic*_*rdt 7

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

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

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


Law*_*Dol 3

在过去的几周里,我有理由调查并纠正线程池对象(Java 6 之前的多线程执行池)的问题,其中启动的线程远多于所需的线程。在相关作业中可能有多达 200 个不必要的线程。线程不断死亡,新的线程取代它们。

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

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

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

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

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