Как может, разбирая текстовый файл 17 МБ в причину Списка OutOfMemory с кучей 128 МБ?

В некоторой части моего заявления я разбираю файл журнала 17 МБ в структуру списка - один LogEntry на строку. Есть приблизительно 100K линии/записи в журнале, означая приблизительно 170 байт на строку. То, что удивило меня, - то, что у меня заканчивается пространство "кучи", даже когда я определяю 128 МБ (256 МБ кажется достаточным). Как может, 10 МБ текста превратились в список причины объектов десятикратное увеличение пространства?

Я понимаю, что объекты Последовательности используют, по крайней мере, дважды сумму пространства по сравнению с текстом ANSI (Unicode, char=2 байты), но это потребляет по крайней мере четыре раза это.

What I am looking for is an approximation for how much an ArrayList of n LogEntries will consume, or how my method might create extraneous objects that aggravate the situation (see comment below on String.trim())

Это - часть данных моего класса LogEntry

public class LogEntry { 
    private Long   id; 
    private String system, version, environment, hostName, userId, clientIP, wsdlName, methodName;
    private Date                timestamp;
    private Long                milliSeconds;
    private Map otherProperties;

Это - часть, делающая чтение

public List readLogEntriesFromFile(File f) throws LogImporterException {
    CSVReader reader;
    final String ISO_8601_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";

    List logEntries = new ArrayList();
    String[] tmp;
    try {
        int lineNumber = 0;
        final char DELIM = ';';
        reader = new CSVReader(new InputStreamReader(new FileInputStream(f)), DELIM);
        while ((tmp = reader.readNext()) != null) {
            lineNumber++;

            if (tmp.length < LogEntry.getRequiredNumberOfAttributes()) {

                String tmpString = concat(tmp);

                if (tmpString.trim().isEmpty()) {
                    logger.debug("Empty string");
                } else {
                    logger.error(String.format(
                            "Invalid log format in %s:L%s. Not enough attributes (%d/%d). Was %s . Continuing ...",
                            f.getAbsolutePath(), lineNumber, tmp.length, LogEntry.getRequiredNumberOfAttributes(), tmpString)
                    );
                }

                continue;
            }

            List values = new ArrayList(Arrays.asList(tmp));
            String system, version, environment, hostName, userId, wsdlName, methodName;
            Date timestamp;
            Long milliSeconds;
            Map otherProperties;

            system = values.remove(0);
            version = values.remove(0);
            environment = values.remove(0);
            hostName = values.remove(0);
            userId = values.remove(0);
            String clientIP = values.remove(0);
            wsdlName = cleanLogString(values.remove(0));
            methodName = cleanLogString(stripNormalPrefixes(values.remove(0)));
            timestamp = new SimpleDateFormat(ISO_8601_DATE_PATTERN).parse(values.remove(0));
            milliSeconds = Long.parseLong(values.remove(0));

            /* remaining properties are the key-value pairs */
            otherProperties = parseOtherProperties(values);

            logEntries.add(new LogEntry(system, version, environment, hostName, userId, clientIP,
                    wsdlName, methodName, timestamp, milliSeconds, otherProperties));
        }
        reader.close();
    } catch (IOException e) {
        throw new LogImporterException("Error reading log file: " + e.getMessage());
    } catch (ParseException e) {
        throw new LogImporterException("Error parsing logfile: " + e.getMessage(), e);
    }

    return logEntries;
}

Сервисная функция, используемая для заселения карты

private Map parseOtherProperties(List values) throws ParseException {
    HashMap map = new HashMap();

    String[] tmp;
    for (String s : values) {
        if (s.trim().isEmpty()) {
            continue;
        }

        tmp = s.split(":");
        if (tmp.length != 2) {
            throw new ParseException("Could not split string into key:value :\"" + s + "\"", s.length());
        }
        map.put(tmp[0], tmp[1]);
    }
    return map;
}
0
nl ja de
За Ingo longs - на самом деле Longs (Объекты), потребляющие 8 байтов для одного только бита Объекта, так вероятно, потребление 16 байтов на Лонга.
добавлено автор oligofren, источник
8 Последовательностей (никакая дата) = 64B, 2 Longs = 16B, 1 Дата (Лонг?) = Объект + Лонг = 16B, Карта с 2*2*10 случайными работами = 160B (см. комментарий к Ingo' s ответ). Это должно составить 256B. Кроме того, прибывает содержание последовательности, скажите 90 случайных работ = 180 байтов. Возможно, байт или два в каждом конце пункта списка, когда вставлено список, так всего где-нибудь линия приблизительно 450 байтов за регистрацию. Вычисления на основе информации от javamex.com/tutorials/memory/object_memory_usage.shtml и обсуждение под Ingo' s ответ
добавлено автор oligofren, источник
@Bimalesh Jha Хм... интересный!
добавлено автор oligofren, источник
Конечно, я могу переместить новый ArrayList из петли и снова использовать его list.clear (), но я don' t думают, что это добавляет много к проблеме здесь.
добавлено автор oligofren, источник
Я вижу, что создание ArrayList в петле могло бы быть немного неэффективным, и вызвать больше работы для GC, но это не должно означать, что у меня должно закончиться пространство "кучи". Слишком много для для GC на самом деле имеет определенное Исключение на Яве, и я видел что в других сценариях:)
добавлено автор oligofren, источник
Я не Явский эксперт, но возможно , новый ArrayList в петле создает слишком много давления памяти для GC, чтобы обращаться, можно ли переместить его вне петли?
добавлено автор Bernd Elkemann, источник
tmpString.trim() в петле может создавать копии последовательности? [javadoc] docs.oracle.com/javase/6/docs/api/java/lang/String.html#trim‌ ​ (). Так же String.format() может также создавать копии?
добавлено автор Bimalesh Jha, источник

2 ответы

У вас также есть Карта там, где вы храните другие свойства. Ваш код не показывает, как эта Карта населена, но имейте в виду, что у Карт может быть значительная память наверху по сравнению с памятью, необходимой для самих записей.

Размер множества, которое поддерживает Карту (по крайней мере 16 записей * 4 байта) + одна пара ключ/значение за вход + размер данных сами. Два записей карты, каждый использующий 10 случайных работ для ключа и 10 случайных работ для стоимости, потребляли бы 16*4 + 2*2*4 + 2*10*2 + 2*10*2 + 2*2*8 = 64+16+40+40+24 = 184 байта (1 случайная работа = 2 байта, объект Последовательности потребляет минуту 8 байтов). Тот один почти удвоил бы космические требования для всей последовательности регистрации.

Добавьте к этому, что LogEntry содержит 12 Объектов, т.е. по крайней мере 96 байтов. Следовательно одним только объектам регистрации были бы нужны приблизительно 100 байтов, плюс-минус некоторые, без Карты и без фактических данных о последовательности. Плюс все указатели для справок (4B каждый). Я считаю по крайней мере 18 с Картой, имея в виду 72 байта.

Добавление данных (-ссылки на объект и объект "заголовки", упомянутые в последнем параграфе):
2 longs = 16B, 1 дата, сохраненная как долго = 8B, карта = 184B. Кроме того, прибывает содержание последовательности, скажите 90 случайных работ = 180 байтов. Возможно, байт или два в каждом конце пункта списка, когда вставлено список, так всего где-нибудь вокруг 100+72+16+8+184+180=560 ~ линия 600 байтов за регистрацию.

Так линия приблизительно 600 байтов за регистрацию, означая 100K линии потреблял бы минимум приблизительно 60 МБ. Это поместило бы его, по крайней мере, в тот же самый порядок величины как размер кучи, который был судебным разбирательством набора. Кроме того, прибывает факт это tmpString.trim() в петле мог бы создавать копии последовательности. Так же String.format() может также создавать копии. Остальная часть применения должна также соответствовать в этом пространстве "кучи" и могла бы объяснить, куда остальная часть памяти идет.

2
добавлено
Вы (и I) путаете указатель (ссылка) размер и сам размер объекта. Если у меня будет 48 байтов указателей, то у меня будут с уверенностью еще 48 байтов объектов, скорее всего больше. Чтобы поместить его по-другому, вам нужны по крайней мере 12 байтов для , на который ссылаются, объект. (Только наверху, никакие данные).
добавлено автор Ingo, источник
Добавьте к этому, что ваш LogEntry содержит 12 Объектов, т.е. по крайней мере (!) 48 байтов. 2 из них - объекты Лонга. (Здесь вы могли спасти немного, если бы вы использовали долго вместо Лонга.) Следовательно одним только вашим объектам регистрации были бы нужны приблизительно 100 байтов, плюс-минус некоторые, без Карта и без фактических данных о последовательности. Это составляет 10 МБ с 100K записями.
добавлено автор Ingo, источник
Можно легко проверить, работает ли это без карты, не населяя его в тестовом прогоне.
добавлено автор Ingo, источник
Размер множества, которое поддерживает Карту (по крайней мере 16 записей * 4 байта) + одна пара ключ/значение за вход + размер данных сами.
добавлено автор Ingo, источник
Согласно javamex.com/tutorials/memory/object_memory_usage.shtml, HotSpot использует 8 байтов за объект, не 4, имея в виду 96 байтов, не 48. Я все еще don' t получают вашу математику, когда вы идете от 48 байтов до 100 байтов, не добавляя данные. Почему вы удваиваете сумму?
добавлено автор oligofren, источник
Я закончил с пунктом приблизительно 450 байтов за регистрацию в моем вычислении выше. Это означало бы приблизительно 45 МБ для 100K записей. Все еще половина моей кучи, но по крайней мере в том же самом заказе размера. Вы могли поместить свои вычисления в ваш ответ, чтобы сделать его немного более полезным?
добавлено автор oligofren, источник
Интересный. Два записей карты 2*2*10 случайных работ (10 байтов для ключа, 10 для стоимости) потребляли бы 16*4 + 2*2*4 + 2*10*2 + 2*10*2 = 64+16+40+40 = 160 байтов (1 случайная работа = 2 байта)! Тот один почти утроил бы космические требования для всей последовательности регистрации.
добавлено автор oligofren, источник
Кодекс добавляется для parseOtherProperties (). Вы могли, возможно, снабдить меня некоторыми числами для того, что мог бы составить этот объект в байтах?
добавлено автор oligofren, источник

Не забывайте, что каждый Последовательность объект занимает место (24 байта?) для фактического Объект определение, плюс ссылка на массив символов, погашение (для подстрока() использование) и т.д. Так представление линии как 'n' последовательности добавит что дополнительное требование хранения. Можно ли лениво оценить их вместо этого в вашем LogEntry класс?

(ре. Последовательность возместила использование - до Явы 7b6 String.substring() действия как окно на существующий массив символов, и следовательно вам нужно погашение. Это недавно изменилось, и это может стоить определить, строит ли более поздний JDK, больше эффективной памяти),

0
добавлено
Получатели/сеттеры сделали бы вычисление.
добавлено автор Ingo, источник
Я предлагаю вас Google или поиск "размера объектов на Яве" и делаю математику самих.
добавлено автор Ingo, источник
Хорошо, достаточно ярмарка. Это будет хорошо для рефакторинга позже;-) Какая-либо идея, как я вычислил бы приблизительно, сколько памяти потребляется текущей версией? Сколько байтов для Даты, Последовательности, ArrayList, Карты, и т.д.
добавлено автор oligofren, источник
Я мог сделать это, но я все еще не могу заставить число совпадать с propertion космического увеличения. BTW объект поднимает минимальные 8 байтов, не 24 (касательно: javamex.com/tutorials/memory/object_memory_usage.shtml)
добавлено автор oligofren, источник
Лениво оценивают, вы подразумеваете, что я просто сохранил бы вход Последовательности в LogEntry, и затем разобрал бы последовательность, когда требуется? Я не уверен, как хорошо, который работал бы с Hibernate' s требования необходимости в получателях/сеттерах, но я мог легко избавиться от, Зимуют во всяком случае.
добавлено автор oligofren, источник
Это прекрасно, но можно показать мне некоторую математику, чтобы показать, как это составляет в целом десятикратное увеличение?
добавлено автор oligofren, источник
pro.jvm
pro.jvm
3 503 участник(ов)

Сообщество разработчиков Java Scala Kotlin Groovy Clojure Чат для нач-их: @javastart Наш сайт: projvm.com projvm.ru Наш канал: @proJVM Вакансии: @jvmjobs Конфы: @jvmconf

Java & Co
Java & Co
2 370 участник(ов)

Можно обсуждать с матом и без всё, что касается жабы, вплоть до холиваров. НЕ ИМЕЕТ ОТНОШЕНИЯ К САЙТУ JAVARUSH.RU ПРАВИЛА - https://t.me/javarush/75723 Вакансии сюда - https://telegram.me/joinchat/B7IzvUCnfo6d8t3yIxKguQ По вопросам - @thedude

learn.java
learn.java
1 888 участник(ов)

Чат для начинающих и не только Статистика: https://combot.org/chat/-1001083535868 Основной чат - @jvmchat

Java Underground
Java Underground
169 участник(ов)

https://vk.com/javatutorial

Javanese Questions
Javanese Questions
109 участник(ов)

Чат предназначен для обмена знаниями строго в формате в вопрос-ответ. Тема — Java, Kotlin и Android. Вопрос должен быть предварительно прогуглен, понятно и грамотно сформулирован, помечен хэштегами. Ответ — тем более. Куски кода размером в несколько строк можно писать прямо здесь, для больших кусков кода стоит использовать http://gist.github.com/, http://pastebin.com/, https://codeshare.io/ или любой аналогичный сервис. В некоторых случаях можно прикреплять скриншоты. Стикеры и гифки запрещены. Дополнять и уточнять вопросы и ответы — редактированием исходного сообщения. Обсуждения должны приводить к редактированию вопроса/ответа и удаляться. По хештегам можно искать существующие вопросы и овтеты: #вопрос #ответ #git #generics #java #server #awt #javafx #swing #kotlin #anko #tornadofx #ktor #android #recyclerView #performance #arch #network #permissions #storage #async