Как стать автором
Обновить
40.1

Демистификация размеров объектов в Java: компактные заголовки, сжатые указатели и многое другое

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров1.4K
Автор оригинала: Peter Lawrey

Понимание размера объектов в Java — важный шаг для оптимизации памяти в высоконагруженных системах. 

В новом переводе от команды Spring АйО вы узнаете про использование HotSpot JVM таких механизмов, как сжатые указатели (Compressed Oops) и компактные заголовки объектов (Compact Object Headers), необходимых для минимизации объема занимаемой памяти.


Введение

Измерение размера объекта в Java — задача не из легких. Платформа побуждает вас учитывать ссылки и абстракции, а не прямое использование памяти. Тем не менее понимание того, сколько памяти занимают объекты, может дать значительные преимущества, особенно для высокопроизводительных систем с низкой задержкой.

За время своего существования JVM внедрила такие оптимизации, как сжатые указатели объектов (Compressed Ordinary Object Pointers, Compressed Oops) и, совсем недавно, компактные заголовки объектов (Compact Object Headers). Каждая из этих технологий может влиять на размер ваших объектов. Понимание этих факторов помогает более четко оценивать использование памяти.

Измерение размеров объектов

Теоретически вы можете оценить размер объекта, создавая экземпляры и наблюдая,  меняется ли количество свободной памяти в JVM. Однако для получения стабильных результатов необходимо нейтрализовать определенные факторы. Например, отключение выделения памяти через TLAB (-XX:-UseTLAB) позволяет наблюдать использование памяти более напрямую.

Комментарий от команды Spring АйО

Благодаря этой настройке происходит отключение использования thread local allocation buffer/block.

Повторные замеры и вычисление медианы могут уменьшить влияние сборки мусора (GC) и параллельного распределения памяти.

Сборка мусора может произойти в процессе создания объекта, это приведёт к тому, что в конце теста будет свободно больше памяти, чем в начале. В этом тесте я игнорирую любые отрицательные значения размеров heap ;) Также другие потоки в системе могут тоже выделять память. Поэтому я провожу несколько тестов и беру медиану, чтобы отбросить аномалии.

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

long before = usedMemory();
Object obj = createYourObject();
long after = usedMemory();
long approximateSize = after - before;

Этот тест SizeofTest.java представляет собой простой пример, который создает несколько объектов и измеряет объем памяти, используемой для создания каждого объекта. Обычно это значение совпадает с объемом памяти, который объект занимает для простых структур.

Область памяти

Описание

Размер (байты)

Mark Word

Заголовок объекта, включая хэш-код, состояние блокировки и метаданные GC

8 байт (на 64-битных JVM)

Class Pointer

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

Обычно 4 байта (с сжатыми указателями классов), иначе 8 байт

Array Length

Хранит длину массива; присутствует только для массивов

4 байта

Instance Fields

Поля экземпляра: примитивы и ссылки

Зависит от типов полей и выравнивания

Padding

Неиспользуемое пространство для обеспечения правильного выравнивания полей объекта на 8 байт

0–7 байт (по мере необходимости)

JEP 450: Компактные заголовки объектов (экспериментальная функция)

Начиная с Java 24 (Early Access), JVM представляет компактные заголовки объектов (Compact Object Headers) в рамках Java Enhancement Proposal 450.

Введение

Сокращение размера заголовков объектов в HotSpot JVM с 96–128 бит до 64 бит на 64-битных архитектурах. Это позволит уменьшить объем используемой кучи, повысить плотность развертывания и улучшить локальность данных.

Цели

При включении эта функция:

  • Обязательно должна сократить размер заголовков объектов до 64 бит (8 байт) на целевых 64-битных платформах (x64 и AArch64);

  • Должна уменьшить размеры объектов и их след в памяти при реальных рабочих нагрузках;

  • Не должна приводить к более чем 5% снижению пропускной способности или увеличению задержки на целевых 64-битных платформах, только в редких случаях;

  • Не должна вызывать измеримого снижения пропускной способности или увеличения задержки на нецелевых 64-битных платформах.

Факторы производительности

Меньший объем объектов может улучшить производительность за счет увеличения количества объектов, помещающихся в кэш CPU, снижения стоимости сборки мусора (GC) и даже сокращения времени запуска. Однако включение (или отключение) этих функций должно сопровождаться тестированием и бенчмаркингом (например, с использованием JMH). В зависимости от вашей рабочей нагрузки эти оптимизации могут дать довольно скромную выгоду или не иметь значительного эффекта.

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

Размер объектов при различных настройках

  • Compact Headers: -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders (в Java 24 EA)

  • Compressed Oops: -XX:+UseCompressedOops (добавлено в Java 8, используется по умолчанию для объема кучи до 32 ГБ)

  • Без Compressed Oops: -XX:-UseCompressedOops (для объемов кучи свыше 32 ГБ)

Таблица 1. Wrappers

Класс/Объект

Compact Header

+Compressed Oops

-Compressed Oops

Object

8

16

16

Boolean, Bytes

16

16

16

Short, Character

16

16

16

Integer, Float

16

16

16

Long, Double

16

24

24

Таблица 2. Простые классы

Класс/Объект

Compact Header

+Compressed Oops

-Compressed Oops

AtomicInteger

16

16

16

AtomicReference

16

16

24

AtomicLong

16

24

24

Optional, SimpleEntry

16

16

24

"Hello World"

24

24

32

CompletableFuture

16

24

32

WeakReference

32

48

64

StringBuilder

56

56

64

Pattern

1056

1088

1240

UUID

216

240

256

Exception

712

728

896

Locale

80

104

120

Таблица 3. Date и Time

Класс/Объект

Compact Header

+Compressed Oops

-Compressed Oops

Date

24

24

32

Timestamp

24

32

32

TimeZone

56

56

80

LocalDate, LocalTime

128

136

168

LocalDateTime

160

184

224

ZonedDateTime

208

232

288

Calendar

528

560

648

Instant, Duration, Period

24

24

24

ZoneId

56

56

80

Таблица 4. Коллекции без элементов, одним и десятью элементами

Класс/Объект

Compact Header

+Compressed Oops

-Compressed Oops

ArrayList

24 / 80 / 80

24 / 80 / 80

32 / 128 / 128

LinkedList

24 / 48 / 264

32 / 56 / 272

40 / 80 / 440

ConcurrentLinkedQueue

32 / 48 / 192

48 / 72 / 288

64 / 96 / 384

ConcurrentHashMap

64 / 168 / 384

64 / 176 / 464

96 / 280 / 384

TreeMap

48 / 80 / 368

48 / 88 / 448

80 / 136 / 640

TreeSet

64 / 96 / 384

64 / 160 / 464

104 / 160 / 664

HashMap

40 / 144 / 360

48 / 160 / 448

64 / 248 / 608

HashSet

56 / 160 / 376

64 / 176 / 464

88 / 272 / 632

LinkedHashMap

56 / 168 / 456

64 / 184 / 544

88 / 288 / 792

LinkedHashSet

72 / 184 / 472

80 / 200 / 560

112 / 312 / 816

Vector, Stack

80 / 80 / 80

88 / 88 / 88

128 / 128 / 128

Hashtable

96 / 120 / 440

112 / 144 / 544

168 / 208 / 768

Таблица 5. Массивы

Класс/Объект

Compact Header

+Compressed Oops

-Compressed Oops

new BitSet(64)

48

48

56

new boolean[64], new byte[64]

80

80

80

new short[64], new char[64]

144

144

144

new int[64], new float[64]

272

272

272

new long[64], new double[64]

528

528

528

new Object[64], new Integer[64], new String[64], new Long[64], new Double[64]

272

272

528

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

Заключение

Абстракции Java часто избавляют вас от необходимости задумываться о нюансах работы с памятью, но понимание того, как размеры объектов меняются при различных конфигурациях JVM, может помочь тонко настроить производительность. Компактные заголовки (Compact Headers) и сжатые указатели объектов (Compressed Oops) — это мощные инструменты, которые позволяют уменьшить объем занимаемой памяти и потенциально повысить эффективность. Эксперименты, замеры и осмысленный бенчмаркинг помогут вам принимать обоснованные решения для ваших конкретных рабочих нагрузок.

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

Рекомендуется начинать с малого: измерьте размеры нескольких объектов, переключайте настройки TLAB (Комментарий от команды Spring АйО: Альтернативное мнение, почему TLAB-ы очень важны: https://shipilev.net/jvm/anatomy-quarks/4-tlab-allocation/) или Compressed Oops (Комментарий от команды Spring АйО: выключение данных настроек может сыграть не на руку, так как по умолчанию при работе с HotSpot JVM они включены) и наблюдайте за изменениями. Со временем вы сформируете более глубокое понимание того, как память работает в Java, что позволит писать более эффективный и предсказуемый код.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм - Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Теги:
Хабы:
+8
Комментарии1

Публикации

Информация

Сайт
t.me
Дата регистрации
Численность
11–30 человек