Что такое накладные расходы на создание объектов Java из строк файла CSV - PullRequest
0 голосов
/ 30 июня 2019

код читает строки файла CSV как:

Stream<String> strings = Files.lines(Paths.get(filePath))

затем он отображает каждую строку в маппере:

List<String> tokens = line.split(","); return new UserModel(tokens.get(0), tokens.get(1), tokens.get(2), tokens.get(3));

и, наконец, собирает его:

Set<UserModel> current = currentStream.collect(toSet())

Размер файла ~ 500 МБ Я подключился к серверу с помощью jconsole и вижу, что при обработке размер кучи вырос с 200 МБ до 1,8 ГБ.

Я не могу понять, откуда пришло использование памяти x3 - я ожидал что-то вроде скачка 500 МБ или около того?

Мое первое впечатление состояло в том, что там нет дросселирования, а сборщик мусора просто не имеет достаточно времени для очистки. Но я пытался использовать ограничитель скорости гуавы, чтобы сборщик мусора успел выполнить свою работу, но результат тот же.

Ответы [ 3 ]

2 голосов
/ 01 июля 2019

Том Хоутин (Tom Hawtin) высказал хорошие замечания - я просто хочу расширить их и предоставить немного больше деталей.

Строки Java занимают как минимум 40 байт памяти (это пустая строка) из-за заголовка объекта Java (см.позже) накладные расходы и внутренний байтовый массив.Это означает, что минимальный размер непустой строки (1 или более символов) составляет 48 байт.

В настоящее время JVM использует Compact Strings , что означает, что строки только для ASCII занимают только 1 байт насимвол - до этого было минимум 2 байта на символ.Это означает, что если ваш файл содержит символы, выходящие за пределы набора ASCII, тогда использование памяти может значительно возрасти.

У потоков также больше накладных расходов по сравнению с простой итерацией с массивами / списками (см. Здесь Объекты потока Java 8 значительно расходуют память)

Я полагаю, ваш объект UserModel добавляет как минимум 32 байта в верхней части каждой строки, потому что:

  • минимальный размер объекта Java составляет 16 байтов, где первые 12байты - это «издержки» JVM: ссылка на класс объекта (4 байта, когда используется сжатые значения ) + слово Mark (используется для хэш-кода идентификатора, смещенная блокировка , сборщики мусора)
  • и следующие 4 байта используются ссылкой на первый «токен»
  • , а следующие 12 байтов используются 3 ссылками на второй, третий и четвертый «токен»
  • и последние 4 байта требуются из-за выравнивания объектов Java на 8-байтовых границах (на 64-разрядных архитектурах)

При этом этоНепонятно, используете ли вы все данные, которые вы читаете из файла - вы разбираете 4 токена со строки, но, возможно, их больше?Более того, вы не упомянули, как именно «вырос» размер кучи - если это был размер commited или размер кучи used.Часть used - это то, что на самом деле «используется» живыми объектами, часть commited - это то, что было выделено JVM в какой-то момент, но позже может быть собрано мусором;used < commited в большинстве случаев.

Вы должны сделать снимок кучи, чтобы узнать, сколько памяти на самом деле занимает результирующий набор UserModel, и это было бы действительно интересно сравнить с размеромфайл.

1 голос
/ 01 июля 2019

Может быть, что реализация String использует UTF-16, тогда как файл может использовать UTF-8.Это будет вдвое больше, если принять все символы ASCII в США.Однако я полагаю, что в настоящее время JVM обычно использует компактную форму для String s.

Еще один фактор заключается в том, что объекты Java имеют тенденцию размещаться по хорошему круглому адресу.Это означает, что есть дополнительное заполнение.

Тогда есть память для фактического String объекта, в дополнение к фактическим данным в основе char[] или byte[].

Тогда есть ваша UserModel объект.Каждый объект имеет заголовок, а ссылки обычно составляют 8 байтов (может быть 4).

Наконец, не вся куча будет выделена.GC работает более эффективно, когда значительная часть памяти в данный момент не используется.Даже C malloc в конечном итоге не использует большую часть памяти, когда процесс запущен и запущен.

0 голосов
/ 30 июня 2019

Ваш код читает полный файл в память. Затем вы начинаете разбивать каждую строку на массив, а затем вы создаете объекты своего пользовательского класса для каждой строки. Таким образом, в основном у вас есть 3 разных «использования памяти» для каждой строки в вашем файле!

Хотя доступно достаточно памяти, jvm может просто не тратить время на сборщик мусора, превращая ваши 500 мегабайт в три разных представления. Следовательно, вы, скорее всего, «утроите» количество байтов в вашем файле. По крайней мере до тех пор, пока gc не включит и не выбросит ненужные строки файлов и разбитые массивы.

...