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 .