openjdk.ruOpenJDK на русском

JEP 387: Elastic Metaspace

эластичный metaspace

ОтветственныйThomas Stuefe
ТипFeature
ОбластьJDK
СтатусClosed / Delivered
Выпуск16
Компонентhotspot / runtime
Обсуждениеhotspot dash runtime dash dev at openjdk dot java dot net
РецензентыColeen Phillimore, Goetz Lindenmaier
ОдобренMikael Vidstedt
Создан2019/03/20 18:22
Обновлён2023/08/15 15:35
Задача8221173

Аннотация

Быстрее возвращать операционной системе неиспользуемую память метаданных классов HotSpot (т. е. metaspace), уменьшить объём памяти, занимаемый metaspace, и упростить код metaspace, чтобы снизить затраты на его сопровождение.

Что не является целью

  • Мы не ставим целью изменить то, как работает кодирование сжатых указателей на классы, или отказаться от области сжатых классов.

  • Мы не ставим целью распространить использование аллокатора metaspace на другие части HotSpot, хотя это может стать улучшением в будущем.

Мотивация

С момента появления в JEP 122 metaspace приобрёл дурную славу из-за высокого потребления памяти вне кучи. У большинства обычных приложений проблем нет, но аллокатор metaspace легко задействовать как раз так, что память будет расходоваться чрезмерно. К сожалению, такие патологические случаи встречаются нередко.

Память metaspace управляется в аренах, по одной на каждый загрузчик классов. Арена содержит один или несколько чанков, из которых её загрузчик выделяет память дешёвым сдвигом указателя. Чанки metaspace крупные, чтобы операции выделения оставались эффективными. Однако из-за этого приложения, использующие много маленьких загрузчиков классов, могут страдать от неоправданно высокого потребления metaspace.

Когда загрузчик классов освобождается, чанки его арены metaspace помещаются в списки свободных блоков для последующего повторного использования. Однако повторное использование может произойти нескоро или не произойти никогда. Поэтому в приложениях, которые интенсивно загружают и выгружают классы, в списках свободных блоков metaspace может скапливаться много неиспользуемого пространства. Это пространство можно вернуть операционной системе для других целей, если оно не фрагментировано, но часто это не так.

Описание

Мы предлагаем заменить существующий аллокатор памяти metaspace на схему выделения памяти методом двойников (buddy allocation). Это давний и проверенный алгоритм, который успешно применяется, например, в ядре Linux. Эта схема позволит на практике выделять память metaspace меньшими чанками, что снизит накладные расходы на загрузчики классов. Она также уменьшит фрагментацию, и мы сможем повысить эластичность, возвращая неиспользуемую память metaspace операционной системе.

Мы также будем выделять (commit) аренам память операционной системы лениво, по запросу. Это уменьшит объём памяти для загрузчиков, которые начинают работу с большими аренами, но используют их не сразу или, возможно, никогда не используют полностью, например для загрузчика классов начальной загрузки (boot class loader).

Наконец, чтобы в полной мере использовать эластичность, которую даёт выделение методом двойников, мы разобьём память metaspace на гранулы одинакового размера, которые можно выделять (commit) и возвращать (uncommit) независимо друг от друга. Размер гранул можно будет задавать новым параметром командной строки, что даёт простой способ управлять фрагментацией виртуальной памяти.

Документ с подробным описанием нового алгоритма находится здесь. Работающий прототип доступен в виде ветки в репозитории JDK sandbox.

Альтернативы

Вместо модернизации metaspace мы могли бы удалить его и выделять метаданные классов прямо из кучи C. Преимуществом такого изменения была бы меньшая сложность кода. Однако у использования аллокатора кучи C были бы следующие недостатки:

  • Как аллокатор на основе арен, metaspace использует то, что объекты метаданных классов освобождаются пакетно. У аллокатора кучи C такой возможности нет, поэтому нам пришлось бы отслеживать и освобождать каждый объект по отдельности. Это увеличило бы накладные расходы во время выполнения и, в зависимости от способа отслеживания объектов, сложность кода и/или потребление памяти.

  • Metaspace выделяет память сдвигом указателя, что даёт очень плотную упаковку памяти. Аллокатор кучи C обычно несёт больше накладных расходов на каждое выделение.

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

  • Чрезмерная зависимость от аллокатора C несёт собственный риск. У аллокаторов кучи C могут быть свои проблемы, например высокая фрагментация и низкая эластичность. Поскольку эти проблемы нам неподконтрольны, их решение требует сотрудничества с поставщиками операционных систем, что может занять много времени и легко свести на нет выигрыш от меньшей сложности кода.

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

В системе Debian с glibc 2.23 мы наблюдали следующие проблемы прототипа на основе malloc:

  • Производительность снизилась на 8–12 % в зависимости от количества и размера загруженных классов.
  • Потребление памяти (RSS процесса) выросло на 15–18 % в пиках загрузки классов перед их выгрузкой.
  • Потребление памяти совсем не возвращалось к прежнему уровню после всплесков, т. е. metaspace был полностью неэластичным. Это привело к разнице в потреблении памяти до 153 %.

Эти наблюдения скрывают дополнительный расход памяти, вызванный отключением области сжатых классов; с его учётом сравнение было бы ещё менее благоприятным для варианта на основе malloc.

Риски и допущения

Фрагментация виртуальной памяти

Каждая операционная система так или иначе управляет своими диапазонами виртуальной памяти; ядро Linux, например, использует красно-чёрное дерево. Возврат (uncommit) памяти может фрагментировать эти диапазоны и увеличить их количество. Это может повлиять на производительность некоторых операций с памятью. В зависимости от ОС это также может привести к тому, что процесс виртуальной машины упрётся в системные ограничения на максимальное количество отображений памяти.

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

Скорость возврата памяти

Возврат больших диапазонов памяти может быть медленным в зависимости от того, как ОС реализует таблицы страниц и насколько плотно диапазон был заполнен до этого. Освобождение metaspace может происходить во время паузы сборки мусора, поэтому это может стать проблемой.

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

Политика освобождения памяти

Чтобы справиться с возможными проблемами, связанными с фрагментацией виртуальной памяти или скоростью возврата памяти, мы добавим новый штатный параметр командной строки, управляющий освобождением памяти metaspace:

`-XX:MetaspaceReclaimPolicy=(balanced|aggressive|none)`
  • balanced: большинство приложений должны получить уменьшение объёма памяти metaspace, а негативные эффекты освобождения памяти должны быть незначительными. Этот режим используется по умолчанию и нацелен на обратную совместимость.
  • «aggressive»: обеспечивает более активное освобождение памяти ценой большей фрагментации виртуальной памяти.
  • «none»: полностью отключает освобождение памяти.

Максимальный размер метаданных

Отдельный объект metaspace не может быть больше размера корневого чанка — наибольшего размера чанка, которым управляет аллокатор методом двойников. Сейчас размер корневого чанка установлен в 4 МБ, что с запасом больше всего, что мы могли бы захотеть выделить в metaspace.