Сборщик мусора Java G1 занимает много памяти

У меня есть проект с большой базой данных. Для разбора я использую java со сборщиком мусора G1. Когда программа работает в течение длительного периода времени, Java начинает потреблять много памяти. Но когда я проверяю кучу Java, размер намного меньше. Например:

  • Java занимает 20 Гб оперативной памяти
  • "jmap -histo" - показывает мне, что куча составляет около 5 Гб оперативной памяти

Вопрос: что занимает остальную часть моей оперативной памяти? Это накладные расходы G1?

Редактировать: вот статистика

Статистика ОЗУ моих Java-процессов: выделено ~50 ГБ, израсходовано ~20 ГБ

информация о jmap: размер кучи ~4 ГБ

java procces: выделено ~50 ГБ, использовано ~20 ГБ
информация о jmap: размер кучи ~4 ГБ


person Igor Kiulian    schedule 03.09.2018    source источник
comment
вот как работает куча JVM, вы указываете -Xmx и -Xms на 20 ГБ, и в настоящее время это занимает всего 5 ГБ...   -  person Eugene    schedule 03.09.2018
comment
@Eugene Я говорю о явном потреблении ОЗУ, -Xmx40G, java потребляет 20G, но размер кучи составляет всего 4G   -  person Igor Kiulian    schedule 03.09.2018
comment
о, так сам процесс потребляет 20G а куча всего 4? как ты измерил эти 20?   -  person Eugene    schedule 03.09.2018
comment
@Eugene, я использую htop для мониторинга процессов, я добавил несколько изображений, чтобы показать   -  person Igor Kiulian    schedule 03.09.2018
comment
Так что, возможно, в какой-то момент времени он действительно использовал 20 ГБ ОЗУ? Или это не куча памяти?   -  person Holger    schedule 03.09.2018
comment
@Holget 20Gb - это текущее состояние. Что может быть некучей памяти? Какая java может хранить размер кучи x3?   -  person Igor Kiulian    schedule 03.09.2018
comment
Да, 20Gb — это «текущее состояние». А нынешнее состояние имеет историю. Когда ему в какой-то момент понадобилось 20Gb, он должен был выделить этот объем памяти, а когда он ему больше не нужен, т.е. содержащиеся в нем объекты были удалены сборщиком мусора, внешние инструменты без знаний о куче Java будут продолжать говорить, что этот процесс выделил этот объем памяти. Тогда как внутри JVM большая часть памяти считается свободной, готовой к заполнению новыми объектами. • Память вне кучи может включать прямые байтовые буферы. Вы можете иметь столько и столько, сколько позволяет доступная оперативная память.   -  person Holger    schedule 04.09.2018


Ответы (2)


Я понял проблему. Как упомянул @Holger, оперативная память выделена для процесса Java, но не полностью заполнена кучей. Но причина, почему G1 выделяет так много оперативной памяти:

G1 страдает, если ему нужно выделить много огромных областей. Они будут создаваться каждый раз, когда размер объекта > 50% размера региона. Они будут растрачивать пространство, так как ничего другого в регионе создаваться не будет. Таким образом, если его размер составляет 51%, вы потеряете 49% области. Хуже того, если размер области составляет 2 МБ, а размер вашего объекта — 2,1 МБ, во втором регионе будет потрачено 1,9 МБ. Если вы выделяете большие объекты, настройте XX:G1HeapRegionSize.

person Igor Kiulian    schedule 05.09.2018

Потребление оперативной памяти будет связано с огромным размером базы данных и размером набора результатов.

Попробуйте следующее: Оптимизация сборки мусора:

  • будьте осторожны с оператором конкатенации строк (+) вместо этого используйте concat()

  • при использовании spring попробуйте setFetchSize (количество строк, которые должны быть извлечены за раз), использование setFetchSize, однако, увеличит время выполнения, но эффективно использует память

  • удалить все ненужные утверждения

  • Используйте асинхронное выполнение

person Anish    schedule 03.09.2018
comment
Спасибо за ваш ответ. Но я не читаю всю базу данных одновременно. Я использую пагинацию: 1) считываю небольшое количество данных - кусками; 2) обработать его; 3) сохранить в БД. Это не должно занимать так много Гб оперативной памяти. И самое загадочное - куча меньше всего потребляет памяти. Вопрос - что жрет эту память, если не куча? - person Igor Kiulian; 03.09.2018
comment
Как вы думаете, почему использование оператора concat имеет преимущество перед использованием оператора +? - person Holger; 03.09.2018
comment
обычно, когда вы указываете concat(), компилятор точно знает, что делать, ( + ) заставляет компилятор понимать контекст, в котором + используется, и + может работать с различными типами данных, интерпретировать его, и не говоря уже о том, что конкатенация строк имеет большой удар по памяти. вы можете использовать concat и toString() вместе. - person Anish; 03.09.2018
comment
@Holger посмотрите это stackoverflow.com/a/8755079/6446770 после прочтения этого, я не понимаю, как concat будет лучше выбор для больших значений - person miiiii; 04.09.2018
comment
@MadMan речь идет только об одном конкретном сценарии, когда у вас есть ровно два элемента для объединения, и оба элемента уже являются String экземплярами. В этом случае String.concat может работать лучше, чем использование StringBuilder, так как его реализация заранее создает массив нужного размера и делает две простые копии входных строк в него и создает новый экземпляр String с этим массивом (защитная копия не требуется) . Однако виртуальные машины JVM оптимизированы специально для типичного использования StringBuilder и устраняют накладные расходы. И Java 9+ будет компилировать оператор + совсем по-другому… - person Holger; 04.09.2018
comment
@Ani, вы путаете работу компилятора и производительность во время выполнения. Весь упомянутый вами анализ будет выполняться во время компиляции и не влияет на производительность во время выполнения. Как только вы комбинируете String.concat с toString() в аргументах, это дороже, чем оператор +, так как эти промежуточные экземпляры String требуют памяти и подразумевают дополнительные операции копирования содержимого символа. Я не знаю, почему вы думаете, что они приходят бесплатно при использовании concat, магии, может быть? - person Holger; 04.09.2018
comment
@ Хольгер, ага. Тот же код, скомпилированный с помощью Java 8 или более ранней версии, может привести к снижению производительности, в то время как тот же код, скомпилированный с помощью JDK9+, будет работать лучше, поскольку в JDK9 улучшена оптимизация времени компиляции для преодоления накладных расходов JIT, верно?? - person miiiii; 04.09.2018
comment
@MadMan Начиная с Java 9, оператор конкатенации строк компилируется в одну инструкцию invokedynamic, которая связывается во время выполнения, следовательно, именно JRE решает, как реализовать конкретный сценарий. Таким образом, если вы используете + ровно с двумя строковыми аргументами, это может быть связано с некоторым кодом, делающим то же самое, что и String.concat. Но еще лучше то, что каждое конкретное созвездие может быть связано со специализированным кодом, генерируемым на лету во время выполнения. Кроме того, он может реализовать кэширование за кулисами, если это будет сочтено полезным. И так далее… - person Holger; 04.09.2018