大型Java列表性能不佳

Jay*_*ker 9 java memory text garbage-collection large-files

我正在尝试使用Java将大型文本语料库读入内存.在某些时候,它撞到墙壁,只是垃圾无休止地收集.我想知道是否有人有经验将Java的GC用于提交大型数据集.

我正在读一个8 GB的英文文本文件,用UTF-8,一行写一行.我希望split()在空格上的每一行,并将结果的String数组存储在一个ArrayList<String[]>进行进一步处理.这是一个展示问题的简化程序:

/** Load whitespace-delimited tokens from stdin into memory. */
public class LoadTokens {
    private static final int INITIAL_SENTENCES = 66000000;

    public static void main(String[] args) throws IOException {
        List<String[]> sentences = new ArrayList<String[]>(INITIAL_SENTENCES);
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        long numTokens = 0;
        String line;

        while ((line = stdin.readLine()) != null) {
            String[] sentence = line.split("\\s+");
            if (sentence.length > 0) {
                sentences.add(sentence);
                numTokens += sentence.length;
            }
        }
        System.out.println("Read " + sentences.size() + " sentences, " + numTokens + " tokens.");
    }
}
Run Code Online (Sandbox Code Playgroud)

看起来很干脆,对吧?你会注意到我甚至预先调整了我的尺寸ArrayList; 我有少于6600万句话和13亿令牌.现在,如果你掏出你的Java对象大小参考和你的铅笔,你会发现应该需要:

  • 66e6 String[]引用@ 8字节ea = 0.5 GB
  • 66e6 String[]对象@ 32字节ea = 2 GB
  • 66e6 char[]对象@ 32字节ea = 2 GB
  • 1.3e9 String引用@ 8字节ea = 10 GB
  • 1.3e9 Strings @ 44字节ea = 53 GB
  • 8e9 chars @ 2字节ea = 15 GB

83 GB.(您会注意到我确实需要使用64位对象大小,因为压缩OOP无法帮助我获得> 32 GB的堆.)我们很幸运拥有一台带有128 GB RAM的RedHat 6机器,所以我开火了从我的Java SE 1.6.0_29套件中获取我的Java HotSpot(TM)64位服务器VM(构建20.4-b02,混合模式),pv giant-file.txt | java -Xmx96G -Xms96G LoadTokens只是为了安全起见,并在我观看时重新启动top.

在输入不到一半的地方,大约50-60 GB RSS,并行垃圾收集器启动高达1300%的CPU(16个proc盒)并且读取进度停止.然后它会增加几GB,然后进度停止更长时间.它填满了96 GB,尚未完成.我已经放了一个半小时,而且只需要90%的系统时间来烧GC.这看起来很极端.

为了确保我没有疯狂,我掀起了相当的Python(所有两行;)并在大约12分钟和70 GB RSS中完成.

那么:我做的事情是愚蠢的吗?(除了通常效率低下的方式存储,我无法真正帮助 - 即使我的数据结构很胖,只要它们适合,Java就不应该只是窒息.)是否有神奇的GC建议真的很大堆?我确实试过-XX:+UseParNewGC,看起来更糟糕.

Jay*_*ker 4

-XX:+UseConcMarkSweepGC:在 78 GB 和约 12 分钟内完成。(几乎和Python一样好!)感谢大家的帮助。