Статьи по Java

Проверка размеров объектов с помощью Java Object Layout

Если кто-то, как и я, задается вопросом “Интересно, сколько места этот объект занимает в памяти?”, то у меня для вас хорошие новости! Java Object Layout (JOL) – это мини-программа, которая может подсчитать, какого размера будет объект, хранящийся в памяти.

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

SerialNumber

Мы начнем с создания класса для представления серийного номера. Сначала он имеет только одно поле типа int:

class SerialNumber { private final int value; SerialNumber(final int value) { this.value = value; } }
Code language: PHP (php)

Примитивные типы

Авторы Java предполагали, что их платформа будет переносимой. Для достижения этой цели им пришлось решить множество проблем. Одним из них были разные размеры типов.

Поэтому виртуальная машина Java имеет предопределенные размеры для примитивных типов:

  • byte – 1 байт
  • short – 2 байт
  • int – 4 байт
  • long – 8 байт
  • float – 4 байт
  • double – 8 байт
  • char – 2 байт
  • boolean – размер зависит от реализации JVM (обычно это 1 байт)

Итак, как и раньше, наш объект SerialNumber должен занимать 4 байта.

Типы объектов

Примитивные типы в Java должны находиться внутри объекта.

Каждый объект строится из примитивных типов или из последовательных типов объектов (которые “в самом низу” состоят из примитивных типов).

Каждый тип объекта хранит информацию о себе (метаданные) в дополнение к значениям полей. Эти метаданные состоят из:

  • class – указатель на тип класса в нашем случае – 4 байта
  • флаги – хранят информацию о состоянии объекта. Среди прочего, форма объекта, т.е. является ли он классом или массивом, и хэш-код – 4 байта
  • lock – монитор, который будет использоваться для mutex – 4 байта
  • size – размер массива (это значение присутствует только для типов массивов) – 4 байта

Имея информацию о типах объектов и примитивах, мы уже знаем, что наш объект должен занимать 12 + 4 = 16 байт. Чтобы проверить это, мы воспользуемся инструментом JOL (сокращение от Java Object Layout).

Java Object Layout

Как я уже писал во введении, JOL – это инструмент для проверки размеров объектов. Давайте начнем с добавления зависимостей в проект:

<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>
Code language: HTML, XML (xml)

В самом начале давайте запишем информацию о нашей текущей виртуальной машине:

System.out.println(VM.current().details());
Code language: CSS (css)

В ответ мы получаем много интересной информации:

# Running 64-bit HotSpot VM. # Using compressed oop with 0-bit shift. # Using compressed klass with 0-bit shift. # Objects are 8 bytes aligned. # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Code language: PHP (php)
  • Мы запускаем нашу программу на 64-битной версии JVM – HotSpot
  • Compressed Oops и Klass будут описаны в будущем
  • Объекты будут выровнены по 8 байтам (это очень важная информация, она будет объяснена далее в статье)
  • Размеры типов в нашей JVM (первое значение в 4 байта – это размер ссылки)

Итак, давайте запишем размер нашего объекта SerialNumber:

final SerialNumber serialNumber = new SerialNumber(123456); System.out.println(ClassLayout.parseClass(SerialNumber.class).toPrintable(serialNumber));
Code language: JavaScript (javascript)

В ответ мы получаем:

pl.codecouple.SerialNumber object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 4 4 (object header) 00 00 00 00 8 4 (object header) 00 14 fd 16 12 4 int SerialNumber.value 123456 Instance size: 16 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
  • 0 – 4 байта – это флаги нашего класса
  • 4 – 8 байт – место для монитора
  • 8 – 12 байт – указатель на тип объекта, в данном случае это my.testcode.SerialNumber
  • 12 – 16 байт наше значение int

Итак, согласно предположениям, наш объект занимает 16 байт памяти.

Выравнивание по 8 байтам

Давайте попробуем добавить в наш класс еще одно поле типа int. Теоретически, наш класс должен занимать 20 байт:

class SerialNumber { private final int value; private final int secondValue; SerialNumber(final int value, int secondValue) { this.value = value; this.secondValue = secondValue; } }
Code language: PHP (php)

Проверяем размер:

my.testcode.SerialNumber object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 4 4 (object header) 00 00 00 00 8 4 (object header) 00 14 c2 16 12 4 int SerialNumber.value 123456 16 4 int SerialNumber.secondValue 1234567 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Наш класс должен занимать 20 байт, но он занимает 24. Это так называемое 8-байтовое выравнивание. Java “снизу” выравнивает объекты по кратным 8 байтам.

Так, в нашем случае ближайшим кратным 8 с 20 является число 24 (это дополнение обусловлено тем, что в текущей JVM “объекты выровнены по 8 байт”).

Информация об этом дополнении также приведена выше.

Контрольный размер

Что если помимо примитивных типов в нашем классе появятся объектные типы? Размер ссылки на объект занимает 4 или 8 байт в зависимости от того, на 32 или 64-битной виртуальной машине Java мы запускаем программу.

Давайте проверим размер объекта с помощью JOL:

class SerialNumber { private final int value; private final int secondValue; private final IntNumber objectValue; SerialNumber(final int value, int secondValue, IntNumber objectValue) { this.value = value; this.secondValue = secondValue; this.objectValue = objectValue; } } class IntNumber { private final int value; IntNumber(int value) { this.value = value; } }
Code language: PHP (php)

Результат:

my.testcode.SerialNumber object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 4 4 (object header) 00 00 00 00 8 4 (object header) 00 14 c7 16 12 4 int SerialNumber.value 123456 16 4 int SerialNumber.secondValue 1234567 20 4 IntNumber SerialNumber.objectValue (object) Instance size: 24 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

Когда я впервые работал с JOL, этот результат показался мне слишком маленьким. Потому что я предполагал, что мой внутренний объект будет занимать 16 байт (12 заголовков + 4 для int). Однако класс ClassLayout возвращает размер только текущего класса без учета внутреннего представления. В данном случае результат правильный, так как ссылка занимает 4 байта.

Graph Layout

Для подсчета общего размера объекта вместе со всеми используемыми внутри типами ссылок мы должны использовать класс GraphLayout:

final SerialNumber serialNumber = new SerialNumber(123456, 1234567, new IntNumber(1)); System.out.println(GraphLayout.parseInstance(serialNumber).toFootprint());
Code language: PHP (php)

На этот раз счет правильный:

my.testcode.SerialNumber@66cd51c3d footprint: COUNT AVG SUM DESCRIPTION 1 16 16 my.testcode.IntNumber 1 24 24 my.testcode.SerialNumber 2 40 (total)
Code language: CSS (css)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *