【问题标题】:java.lang.OutOfMemoryError: GC overhead limitjava.lang.OutOfMemoryError:GC 开销限制
【发布时间】:2013-08-19 22:31:31
【问题描述】:

我有一个程序可以从文件中读取大量序列,并在该列表中的所有对之间进行计算。然后它将所有这些计算存储到一个哈希集中。在运行该程序大约一半时,我收到 GC 开销限制错误。

我意识到这是因为垃圾收集器使用了 98% 的计算时间,甚至无法恢复 2% 的堆。这是我的代码:

ArrayList<String> c = loadSequences("file.txt"); // Loads 60 char DNA sequences
HashSet<DNAPair,Double> LSA = new HashSet<DNAPair,Double>(); 
for(int i = 0; i < c.size(); i++) {
    for(int j = i+1; j < c.size(); j++) {
        LSA.put(new DNAPair(c.get(i),c.get(j)),localSeqAlignmentSimilarity(c.get(i),c.get(j)));
    }
}

下面是实际方法的代码:

public static double localSeqAlignmentSimilarity(String s1, String s2) {
    s1 = " " + s1;
    s2 = " " + s2;
    int max = 0,h = 0,maxI = 0,maxJ = 0;

    int[][] score = new int[61][61];
    int[][] pointers = new int[61][61];

    for(int i = 1; i < s1.length(); i++) {
        pointers[i][0] = 2;
    }
    for(int i = 1; i < s2.length(); i++) {
        pointers[0][i] = 1;
    }

    boolean inGap = false;
    for(int i = 1; i < s1.length(); i++) {
        for(int j = 1; j < s2.length();  j++) {
            h = -99;
            if(score[i-1][j-1] + match(s1.charAt(i),s2.charAt(j)) > h) {
                h = score[i-1][j-1] + match(s1.charAt(i),s2.charAt(j));
                pointers[i][j] = 3;
                inGap = false;
            } 
            if(!inGap) {
                if(score[i-1][j] + GAPPENALTY > h) {
                    h = score[i-1][j] + GAPPENALTY;
                    pointers[i][j] = 2;
                    inGap = true;
                } 
                if(score[i][j-1] + GAPPENALTY > h) {
                    h = score[i][j-1] + GAPPENALTY;
                    pointers[i][j] = 1;
                    inGap = true;
                }
            } else {
                if(score[i-1][j] + GAPEXTENSION > h) {
                    h = score[i-1][j] + GAPEXTENSION;
                    pointers[i][j] = 2;
                    inGap = true;
                } 
                if(score[i][j-1] + GAPEXTENSION > h) {
                    h = score[i][j-1] + GAPEXTENSION;
                    pointers[i][j] = 1;
                    inGap = true;
                }
            }

            if(0 > h) h = 0;

            score[i][j] = h;
            if(h >= max) {
                max = h;
                maxI = i;
                maxJ = j;
            }
        }
    }

    double matches = 0;
    String o1 = "",  o2 = "";
    while(!(maxI == 0 && maxJ == 0)) {
        if(pointers[maxI][maxJ] == 3) {
            o1 += s1.charAt(maxI);
            o2 += s2.charAt(maxJ);
            maxI--;
            maxJ--;
        } else if(pointers[maxI][maxJ] == 2) {
            o1 += s1.charAt(maxI);
            o2 += "_";
            maxI--;
        } else if(pointers[maxI][maxJ] == 1) {
            o1 += "_";
            o2 += s2.charAt(maxJ);
            maxJ--;
        }
    }

    StringBuilder a = new StringBuilder(o1);
    b = new StringBuilder(o2);
    o1 = a.reverse().toString();
    o2 = b.reverse().toString();
    a.setLength(0);
    b.setLength(0);

    for(int i = 0; i < Math.min(o1.length(), o2.length()); i++) {
        if(o1.charAt(i) == o2.charAt(i)) matches++;
    }
    return matches/Math.min(o1.length(), o2.length());
}

我认为这是因为我在方法中声明的所有变量(两个 int 数组和 stringbuilder 等)每次运行该方法时都会创建越来越多的对象,所以我将它们全部更改为静态字段并清除每次都使用它们(例如 Arrays.fill(score,0);),而不是创建一个新对象。

但是这根本没有帮助,我仍然遇到同样的错误。

会不会是存储所有计算的hashset太大而无法被java存储?我没有收到堆空间不足错误,所以看起来有点奇怪。

我还更改了命令行参数以给 JVM 更多空间,但这似乎没有帮助。

对此问题的任何见解都会有所帮助。谢谢!

【问题讨论】:

  • 你从文件中读取了多少序列?
  • DNAPair 是否覆盖 hashCode?您正在创建一个很快就会变大的 n^2 集合。您可能必须将集合交换到磁盘。
  • 我使用的特定文件有 73657 个序列。

标签: java garbage-collection


【解决方案1】:

这是一个问题,如果 c.size() 是 73657 并且序列是唯一的:

HashSet<DNAPair,Double> LSA = new HashSet<DNAPair,Double>(); 
 for(int i = 0; i < c.size(); i++) {
   for(int j = i+1; j < c.size(); j++) {
      LSA.put(...);
   }
 }

假设这些是唯一的序列,您基本上是在为每一对添加一个元素到 LSA。你提到你有 70k 序列,所以你将有 70k * 70k = ~50 亿对,每对至少需要 4 个字节来存储,这意味着你至少需要分配 20+ GB 来存储是可行的。

【讨论】:

  • 这是有道理的,存储这些对的替代方法是什么?这部分代码只是预先计算了这些对以供以后在程序中使用。有没有更好的方法可以存储数据?
【解决方案2】:

是的,确实可能是数据量太大而无法存储在内存中。我将首先尝试使用 JConsole 之类的东西来实际分析程序在运行时的内存使用情况,或者通常从您的程序中读取 MemoryMXBean。

如果有用,我写了一个小的 Classmexer 代理,它允许您从 Java 程序中查询 Java 对象(和子对象)的实际内存使用情况。

顺便说一句,尝试“愚弄”或抢占 JVM 的内存管理系统通常是没有好处的,比如将对象设置为静态的,而实际上不应该是静态的。

【讨论】:

    猜你喜欢
    • 2012-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-21
    • 2016-08-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多