Низкая производительность при работе с большими списками Java

Я пытаюсь прочитать большой текстовый корпус в памяти с помощью 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 и свой карандаш, вы обнаружите, что для этого требуется около:

  • 66e6 String[] ссылок @ 8 байтов ea = 0,5 ГБ
  • 66e6 String[] объектов @ 32 байта ea = 2 ГБ
  • 66e6 char[] объектов @ 32 байта ea = 2 ГБ
  • 1.3e9 String ссылки @ 8 байтов ea = 10 ГБ
  • 1.3e9 Strings @ 44 байта ea = 53 ГБ
  • 8e9 chars @ 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, и это кажется еще хуже.

9
задан Jay Hacker 7 March 2012 в 15:42
поделиться