Я пытаюсь прочитать большой текстовый корпус в памяти с помощью Java. В какой-то момент он ударяется о стену, и просто бесконечно накапливается мусор. Я хотел бы знать, есть ли у кого-нибудь опыт победы над сборщиком мусора Java при отправке больших наборов данных.
Я читаю 8-гигабайтный текстовый файл на английском языке в кодировке UTF-8, по одному предложению в строке. Я хочу split()
каждую строку с пробелами и сохранить полученные массивы строк в ArrayList
для дальнейшей обработки. Вот упрощенная программа, демонстрирующая проблему:
/** 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 sentences = new ArrayList(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
; У меня чуть меньше 66 миллионов предложений и 1,3 миллиарда токенов. Теперь, если вы достанете свой справочник размеров объектов Java и свой карандаш, вы обнаружите, что для этого требуется около:
String[]
ссылок @ 8 байтов ea = 0,5 ГБ String[]
объектов @ 32 байта ea = 2 ГБchar[]
объектов @ 32 байта ea = 2 ГБ String
ссылки @ 8 байтов ea = 10 ГБString
s @ 44 байта ea = 53 ГБchar
s @ 2 байта ea = 15 ГБ83 ГБ. (Вы заметите, что мне действительно нужно использовать 64-битные размеры объектов, так как Сжатые ООП не могут помочь мне с кучей> 32 ГБ.) Нам повезло, что у нас есть машина RedHat 6 с 128 ГБ ОЗУ, поэтому я запускаю свою 64-битную виртуальную машину Java HotSpot™ (сборка 20.4-b02, смешанный режим) из моего комплекта Java SE 1.6.0_29 с pv гигантский файл.txt | java -Xmx96G -Xms96G LoadTokens
просто на всякий случай и откиньтесь назад, пока я смотрю top
.
Где-то менее чем на полпути ввода, примерно при 50-60 ГБ RSS, параллельный сборщик мусора загружает ЦП до 1300% (16 proc box) и процесс чтения останавливается. Потом уходит еще несколько Гб, потом прогресс останавливается еще дольше. Он заполняет 96 ГБ и еще не готов. Я оставил его на полтора часа, и он просто сжигает ~ 90% системного времени на сборку мусора. Это кажется экстремальным.
Чтобы удостовериться, что я не сошел с ума, я на скорую руку набросал аналогичный Python (все две строки ;), и он был завершен примерно за 12 минут и 70 ГБ RSS.
Итак: я делаю что-то глупое? (Помимо в целом неэффективного способа хранения вещей, с которым я не могу помочь — и даже если мои структуры данных толстые, пока они подходят, Java не должна просто задыхаться. ) Есть ли волшебный совет GC для действительно больших куч? Я пробовал -XX:+UseParNewGC
, и это кажется еще хуже.