我正在尝试使用 Java 将大型文本语料库读入内存。在某些时候它撞到了一堵墙,只是无休止地收集垃圾。我想知道是否有人有击败 Java 的 GC 以提交大型数据集的经验。
我正在阅读一个 8 GB 的英文文本文件,采用 UTF-8 格式,一行一行。我要split()
每行放在空白处,并将生成的字符串数组存储在 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.");
}
}
看起来很简单,对吧?你会注意到我什至预先调整了我的 ArrayList
;我有不到 6600 万个句子和 13 亿个标记。现在如果你拿出你的Java object sizes引用和你的铅笔,你会发现应该需要:
- 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
String
s @ 44 字节 ea = 53 GB - 8e9
char
s @ 2 字节 ea = 15 GB
83 GB。 (您会注意到我确实需要使用 64 位对象大小,因为 Compressed OOPs 无法帮助我处理 > 32 GB 的堆。)我们很幸运拥有一台具有 128 GB RAM 的 RedHat 6 机器,所以我从我的 Java SE 1.6.0_29 工具包中使用 pv giant-file.txt | java -Xmx96G -Xms96G LoadTokens
启动我的 Java HotSpot(TM) 64 位服务器 VM(构建 20.4-b02,混合模式)为了安全起见,在我观看 top
时退后一步.
在输入不到一半的地方,大约 50-60 GB RSS,并行垃圾收集器将 CPU 提高到 1300%(16 个 proc 框)并停止读取进度。然后它又增加了几 GB,然后进度停止了更长的时间。它已填满 96 GB,但尚未完成。我已经放了一个半小时,它只是消耗了大约 90% 的系统时间来做 GC。这似乎很极端。
为了确保我没有发疯,我编写了等效的 Python(所有两行;),它在大约 12 分钟内运行完成,RSS 为 70 GB。
那么:我是在做蠢事吗? (除了存储东西的通常效率低下,我真的帮不上忙——即使我的数据结构很胖,只要它们适合,Java 不应该只是窒息 .) 对于非常大的堆,是否有神奇的 GC 建议?我试过-XX:+UseParNewGC
而且看起来更糟。
最佳答案
-XX:+UseConcMarkSweepGC
:在 78 GB 和 ~12 分钟内完成。 (几乎和 Python 一样好!)感谢大家的帮助。
关于java - 大型 Java 列表性能不佳,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9593555/