Хабрахабр

Сжатие указателей в Java

В статье речь пойдет о реализации сжатия указателей в Java Virtual Machine 64-bit, которое контролируется опцией UseCompressedOops и включено по-умолчанию для 64 битных систем начиная с Java SE 6u23.

Это может увеличивать размер данных в 1,5 раза по сравнению с аналогичным кодом для 32 битной архитектуры. В 64 битной JVM указатели занимают в 2 раза больше (сюрприз-сюрприз) места в памяти чем в 32 битной. При этом в 32 битной архитектуре можно адресовать только 2^32 байт (4 ГБ), что довольно мало в современном мире.

Давайте напишем небольшую программу и посмотрим на то, сколько байт занимают объекты Integer:

import java.util.stream.IntStream;
import java.util.stream.Stream; class HeapTest
}

Последняя строка нужна для того, чтобы компилятор вдруг не проигнорировал создание массива (хотя на моей машине без этой строки объекты создаются нормально). Здесь мы выделяем миллион объектов класса Integer и надолго засыпаем.

Компилируем и запускаем программу с отключенным сжатием указателей:

> javac HeapTest.java
> java -XX:-UseCompressedOops HeapTest

С помощью утилиты jcmd смотрим распределение памяти:

> jps
45236 HeapTest
... > jcmd 45236 GC.class_histogram

Т.е.
На картинке видно, что общее количество объектов равно 1000128, а размер памяти, который занимают эти объекты 24003072 байт. 24 байта на объект (почему именно 24 будет написано ниже).

А вот память той же программы, но с включенным флагом UseCompressedOops:

Теперь каждый объект занимает 16 байт.
Плюсы сжатия очевидны =)

Эта техника называется Compressed Oops. Как же JVM сжимает указатели? Oop расшифровывается как ordinary object pointer или обычный указатель на объект.

по 8 байт. Трюк состоит в том, что в 64 битной системе данные в памяти выровнены по машинному слову, т.е. И адрес всегда имеет три нулевых бита в конце.

адресовать до 32 ГБ (2^35 байт). Если при сохранении указателя сдвигать адрес на 3 бита вправо (операция называется encode), а перед использованием сдвигать на 3 бита влево (соответственно decode), то можно уместить в 32-х битах указатели размером в 35 бит, т.е.

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

Когда опция UseCompressedOops включена, то сжимаются следующие типы указателей:

  • Поле класса для каждого объекта
  • Объекты поля класса
  • Элементы массива объектов.

При этом сжатие происходит на уровне виртуальной машины, а не байт-кода. Объекты самой JVM никогда не сжимаются.

А теперь давайте с помощью утилиты jol (Java Object Layout) посмотрим внимательнее на то, сколько памяти занимает наш Integer в разных JVM :

> java -jar jol-cli-0.9-full.jar estimates java.lang.Integer ***** 32-bit VM: **********************************************************
java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 8 (object header) N/A 8 4 int Integer.value N/A 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ***** 64-bit VM: **********************************************************
java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 16 (object header) N/A 16 4 int Integer.value N/A 20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ***** 64-bit VM, compressed references enabled: ***************************
java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Integer.value N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total ***** 64-bit VM, compressed references enabled, 16-byte align: ************
java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Integer.value N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

Плюс, в случае без сжатия, появляется необходимость добавить еще 4 байта для выравнивания данных в памяти. Разница между "64-bit VM" и "64-bit VM, compressed references enabled" состоит в уменьшении object header (заголовок) на 4 байта.

Почему он уменьшился на 4 байта? Что такое этот object header?

с включенной опцией UseCompressedOops. На картинке изображен object header, равный 12 байтам, т.е. Видно, что указатель на класс занимает 32 бита. Заголовок состоит из некоторых внутренних флагов JVM, а так же указателя на класс данного объекта. Без сжатия он занимал бы 64 бита и размер object header был бы уже 16 байт.

В таком случае можно увеличить память до 64 ГБ. Кстати, можно заметить, что есть еще вариант для 16-байтового выравнивания.

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

К примеру вот график пауз сборщика мусора для сжатых и не сжатых указателей, взятый отсюда Java GC in Numbers — Compressed OOPs

Более подробно об этом можно почитать в самой статье (статья довольно старая — 2013 года). Видно, что при включенном сжатии, GC паузы длятся дольше.

Ссылки:

Compressed oops in the Hotspot JVM
How does JVM allocate objects
CompressedOops: Introduction to compressed references in Java
Trick behind JVM's compressed Oops
Java HotSpot Virtual Machine Performance Enhancements

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть