【问题标题】:Java garbage collector performance for allocating/deallocating memory inside loop用于在循环内分配/释放内存的 Java 垃圾收集器性能
【发布时间】:2011-05-24 18:22:24
【问题描述】:

我有一个程序,其中有问题的循环看起来像这样

int numOfWords = 1000;
int avgSizeOfWord = 20;
while(all documents are not read) {
    char[][] wordsInDoc = new char[numOfWords][avgSizeOfWord];
    for(int i=0; i<numWordsInDoc; i++) {
        wordsInDoc[i] = getNextWord();
    }
    processWords(wordsInDoc);
}

我想知道当这个循环执行时幕后会发生什么。垃圾收集器何时收集已分配给每个文档的内存?他们是做同样事情的更好方法(wrt 内存使用)吗?

感谢任何见解。

【问题讨论】:

  • Java 有一个不确定的垃圾收集器,这意味着无法仅通过查看一段代码来预测其行为。
  • 您的问题可能更多是关于 JIT 而不是 GC 算法,即如果此代码由 JIT 编译,那么您的代码版本是什么样的?例如 processWords 是做什么的?它会内联吗?如果是这样,wordInDoc 会被提升到循环之外吗?有没有可能被吊起来?可能还有其他一些可能的编译途径......
  • @Matt 我需要研究 JIT 的工作原理,因为我完全不知道这一点。但是我没有理解这个特定句子的含义如果有,wordsInDoc 会被提升到循环之外吗? 比如说,processWords 将单词写入文件,那么场景是什么?跨度>
  • 我建议从这个包中阅读 TS-5427 -- download.oracle.com/javaone/javaone2009-core-se.zip -- 这是 JavaOne 关于“Inside Out - A Modern Virtual Machine Revealed”的演讲

标签: java memory-management garbage-collection


【解决方案1】:

一般来说不可能回答您的问题,因为 JVM 几乎可以在垃圾收集方面做任何想做的事情。

通过在诸如YourKit 之类的内存分析器下运行您的程序,您或许能够深入了解实际发生的情况。这也将使您能够比较不同的策略(例如,使用 String 类而不是 char 数组)在内存使用和垃圾收集器中花费的时间方面。

【讨论】:

    【解决方案2】:

    嗯,你肯定是在浪费内存——你分配了所有的“子数组”,然后覆盖了它们。你最好:

    while(all documents are not read) {
        char[][] wordsInDoc = new char[numOfWords][];
        for(int i=0; i < numWordsInDoc; i++) {
            wordsInDoc[i] = getNextWord();
        }
        processWords(wordsInDoc);
    }
    

    现在processWords 实际上做了什么?如果它没有将数组存储在任何地方,您可以重复使用它:

    char[][] wordsInDoc = new char[numOfWords][];
    while(all documents are not read) {
        for(int i=0; i < numWordsInDoc; i++) {
            wordsInDoc[i] = getNextWord();
        }
        processWords(wordsInDoc);
    }
    

    我肯定会执行第一个更改,但可能不会第二个。

    至于垃圾回收的确切时间 - 这是特定于实现的。

    【讨论】:

    • @kprotocol:如果单词被快速读入、处理和写出,它们可能永远不会超过第一代,所以在 GC 命中方面不会太糟糕。如果 getNextWord 已经在分配一个 char 数组,那就有它自己的影响。现在,如果您也可以重用所有这些数组(也许使用 Unicode 空字符来指示单词的结尾),您几乎可以完全消除分配。另一方面,您是否有任何证据表明这是一个重大的性能瓶颈? (继续。)
    • 我通常会先写出我能做到的最简单代码,然后对其进行分析以找出需要优化的地方。如果您有一个实际的负载需要首先进行测试 - 以及具体的性能要求(不仅仅是“尽可能快”),这样您就知道什么时候完成了。
    【解决方案3】:

    很可能你正在创建你要立即销毁的数组。更有效的方法是创建普通数组,或者使用 List。

    char[][] wordsInDoc = new char[numOfWords][];
    for(int i=0; i<numWordsInDoc; i++) {
        wordsInDoc[i] = getNextWord();
    }
    processWords(wordsInDoc);
    

    List<char[]> wordsInDoc = new ArrayList<char[]>();
    for(int i=0; i<numWordsInDoc; i++) {
        wordsInDoc.add(getNextWord());
    }
    processWords(wordsInDoc);
    

    或者使用字符串

    String line = "Hello World. This is a Sentence";
    String[] words = line.split(" +");
    processWords(words);
    

    【讨论】:

      【解决方案4】:

      垃圾收集器以神秘的方式工作。即使直接调用它也只是一个建议。

      如果您想了解某个对象何时被垃圾回收,您可以覆盖 finalize() 并在时间上记录输出信息。

      【讨论】:

        【解决方案5】:

        我的几分钱:)

        1. 我猜当您声明一个数组时,与在 C/C++ 中不同的是,您实际上并没有为对象保留内存,而是您简单地创建了那么多引用。
        2. 每个引用可能占用一定的内存(这肯定会小于其指向的对象占用的内存)。因此,无论您使用普通数组还是 ArrayList(它们做同样的事情,但以类型安全的方式)都无关紧要。
        3. 上述方法的一个非常基本的问题是,它将整个文档加载到内存中并将其发送给处理。
        4. 更好/更有效的方式将其流式传输(缓冲)然后动态处理。这将防止整个文档被加载到内存中。

        关于 GC,正如这里的人们所指出的,它无法预测。每当 JVM 内存不足时它就会启动,但这只是一句陈词滥调:)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-05-15
          • 1970-01-01
          • 2012-04-08
          • 2021-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多