Перейти к основному содержимому

boolean и boolean[] Разметка памяти в JVM

· 5 мин. чтения

1. Обзор

В этой быстрой статье мы увидим, каково влияние логического значения в JVM при различных обстоятельствах.

Сначала мы проверим JVM, чтобы увидеть размеры объектов. Тогда мы поймем обоснование этих размеров.

2. Настройка

Чтобы проверить расположение объектов в памяти в JVM, мы будем широко использовать Java Object Layout ( JOL ). Поэтому нам нужно добавить зависимость jol-core : ``

<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>

3. Размеры объектов

Если мы попросим JOL распечатать сведения о виртуальной машине с точки зрения размеров объектов:

System.out.println(VM.current().details());

Когда сжатые ссылки включены (поведение по умолчанию), мы увидим вывод:

# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-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]

В первых нескольких строках мы можем увидеть некоторую общую информацию о виртуальной машине. После этого узнаем о размерах объектов:

  • Ссылки Java занимают 4 байта, boolean s / bytes — 1 байт, char s/ short — 2 байта, int s/ float — 4 байта и, наконец, long s/ double s — 8 байт. ``
  • Эти типы потребляют одинаковое количество памяти, даже когда мы используем их как элементы массива.

Итак, при наличии сжатых ссылок каждое логическое значение занимает 1 байт. Точно так же каждое логическое значение в boolean[] занимает 1 байт. Однако отступы выравнивания и заголовки объектов могут увеличить пространство, занимаемое логическими значениями и логическими значениями [] , как мы увидим позже.

3.1. Нет сжатых ссылок

Даже если мы отключим сжатые ссылки с помощью -XX:-UseCompressedOops , логический размер вообще не изменится :

# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

С другой стороны, ссылки Java занимают вдвое больше памяти.

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

3.2. Разрыв слов

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

Одной из целей разработки JVM является предотвращение этого явления, известного как разрыв слов . То есть в JVM каждое поле и элемент массива должны быть разными; обновления одного поля или элемента не должны взаимодействовать с чтением или обновлением любого другого поля или элемента.

Напомним, что проблемы адресации и разрывы слов являются основными причинами, по которым логические значения представляют собой нечто большее, чем один бит.

4. Обычные указатели объектов (ООП)

Теперь, когда мы знаем, что логические значения равны 1 байту, давайте рассмотрим этот простой класс:

class BooleanWrapper {
private boolean value;
}

Если мы проверим расположение памяти этого класса с помощью JOL:

System.out.println(ClassLayout.parseClass(BooleanWrapper.class).toPrintable());

Затем JOL напечатает макет памяти:

OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
0 12 (object header) N/A
12 1 boolean BooleanWrapper.value N/A
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

Макет BooleanWrapper состоит из:

  • 12 байтов для заголовка, включая два слова метки и одно слово класса . HotSpot JVM использует слово метки для хранения метаданных сборщика мусора, идентификационного хэш-кода и информации о блокировке. Кроме того, он использует слово klass для хранения метаданных класса, таких как проверки типа во время выполнения.
  • 1 байт для фактического логического значения
  • 3 байта заполнения для целей выравнивания

По умолчанию ссылки на объекты должны быть выровнены по 8 байтам. Поэтому JVM добавляет 3 байта к 13 байтам заголовка и логического значения , чтобы получить 16 байтов.

Следовательно, логические поля могут потреблять больше памяти из-за их выравнивания полей.

4.1. Пользовательское выравнивание

Если мы изменим значение выравнивания на 32 с помощью -XX:ObjectAlignmentInBytes=32, то тот же макет класса изменится на:

OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
0 12 (object header) N/A
12 1 boolean BooleanWrapper.value N/A
13 19 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 19 bytes external = 19 bytes total

Как показано выше, JVM добавляет 19 байтов заполнения, чтобы сделать размер объекта кратным 32.

5. Массив ООП

Давайте посмотрим, как JVM размещает логический массив в памяти:

boolean[] value = new boolean[3];
System.out.println(ClassLayout.parseInstance(value).toPrintable());

Это напечатает макет экземпляра следующим образом:

OFFSET  SIZE      TYPE DESCRIPTION                              
0 4 (object header) # mark word
4 4 (object header) # mark word
8 4 (object header) # klass word
12 4 (object header) # array length
16 3 boolean [Z.<elements> # [Z means boolean array
19 5 (loss due to the next object alignment)

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

Поскольку наш массив состоит из трех элементов, размер элементов массива равен 3 байтам. Однако эти 3 байта будут дополнены 5 байтами выравнивания полей для обеспечения правильного выравнивания.

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

6. Заключение

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

Для более подробного обсуждения настоятельно рекомендуется ознакомиться с разделом oops исходного кода JVM. Также у Алексея Шипилева есть гораздо более глубокая статья на эту тему.

Как обычно, все примеры доступны на GitHub .