Статьи

CompressedOops: введение в сжатые ссылки в Java

В этой статье мы познакомим вас с одной из оптимизаций JVM, известной как Compressed oops. Идея сжатых операций возникает из различий между 32-битной и 64-битной архитектурой. Итак, у нас будет очень краткий обзор 64-битной архитектуры, а затем мы углубимся в тему сжатых операций. В конце мы увидим все в действии на простом примере.

Пример кода для этой статьи довольно прост, поэтому мы не собираемся использовать IDE. Сжатый ой не имеет смысла на 32-битной машине. Также он не активирован по умолчанию в JDK до 6u23. Поэтому мы предполагаем, что вы используете 64-битный JDK новее, чем 6u23. Последний инструмент, который нам нужен, — это анализатор памяти. Для этого примера мы использовали стандартный инструмент Eclipse Memory Analyzer версии 1.5.

1. 32 бит против 64 бит

32-битные и 64-битные были в моде в начале 2000-х годов. Хотя 64-битный процессор не был чем-то новым в мире суперкомпьютеров, лишь недавно персональные компьютеры стали популярными. Переход с 32-битной архитектуры на 64-битную — далеко не простая работа, все от оборудования до операционной системы должно измениться. Java приняла этот переход с введением 64-битной виртуальной машины.

Основным преимуществом этого перехода является объем памяти. В 32-битной системе ширина адреса памяти составляет 32 бита (отсюда и название), что означает, что общий объем адресуемой памяти составляет 2 ^ 32 или 4 гигабайта оперативной памяти. В прошлом это могло быть бесконечное количество памяти для персонального компьютера (в конце концов, кому требуется более 640 КБ ОЗУ!), Но не в то время, когда смартфон с одним гигабайтом памяти считается продуктом низкого уровня. 64-битная архитектура решила это ограничение. В такой машине теоретический объем адресуемой памяти составляет 2 ^ 64, смехотворно огромное количество. К сожалению, это всего лишь теоретический предел, в реальном мире существует множество аппаратных и программных факторов, которые ограничивают нас гораздо меньшим объемом памяти. Например, Windows 7 Ultimate поддерживает только до 192 ГБ. Использование слова только для 192 гигабайт кажется немного резким, но оно меркнет по сравнению с 2 ^ 64. Теперь, когда вы понимаете, почему 64-битные значения имеют значение, давайте перейдем к следующей части и посмотрим, как сжатые операции помогут нам.

2. Сжатый ой в теории

«Нет такого понятия, как бесплатный обед». Чрезмерный объем памяти на 64-битных машинах имеет свою цену. Обычно приложение потребляет больше памяти в 64-битной системе, и в нетривиальном приложении эта сумма не является незначительной. Сжатые операции помогают вам зарезервировать часть памяти, используя 32-битные указатели классов в 64-битной среде, при условии, что размер кучи не будет больше 32 ГБ. Чтобы увидеть это более подробно, давайте посмотрим, как объект представлен в Java.

2.1. Представление объекта в Java

Чтобы увидеть, как объекты представлены в Java, мы используем очень простой пример, объект Integer, который содержит примитив int. Когда вы пишете простую строку кода, как показано ниже:

1
Integer i = new Integer(23);

Компилятор выделяет этому объекту гораздо больше 32 бит кучи. в Java длина 32 бита, но у каждого объекта есть заголовки. Размер этих заголовков отличается в 32-битных и 64-битных и разных виртуальных машин. В 32-битной виртуальной машине каждое из этих полей заголовка представляет собой одно слово или 4 байта. В 64-битной виртуальной машине поле, содержащее int, остается 32-битным, но размер других полей удваивается до 8 байт (одно слово в 64-битной среде). На самом деле история не заканчивается здесь. Объекты выровнены по словам, что означает, что на 64-битной машине объем памяти, который они занимают, должен делиться на 64. Для нас основной интерес представляет размер указателя класса, который известен как Klass на языке Hotspot VM. Как вы можете видеть на рисунке ниже, размер klass составляет 8 байтов на 64-битной виртуальной машине, но с включенным сжатым параметром становится 4 байта.

Представление объекта Integer в разных виртуальных машинах

Представление объекта Integer в разных виртуальных машинах

2,3. Как реализован сжатый упс

oop в сжатом oops означает обычный указатель объекта. Эти указатели объектов (как мы видели в предыдущем разделе) имеют тот же размер, что и собственные указатели машины. Таким образом, размер ой составляет 32 или 64 бита на 32-битной и 64-битной машине соответственно. С сжатыми упсами у нас есть 32-битные указатели на 64-битной машине.

Хитрость в сжатых операциях заключается в разнице между адресацией байтов и адресацией памяти в памяти. С помощью байтовой адресации у вас есть доступ к каждому байту в памяти, но вам также нужен уникальный адрес для каждого байта. В 32-битной среде это ограничивает вас до 2 ^ 32 байтов памяти. При адресации слов у вас все еще остается одинаковое количество адресуемых блоков памяти, но этот фрагмент памяти представляет собой одно слово вместо одного байта. В 64-битной машине слово составляет 8 байтов. Это дает JVM три нулевых бита. Java использует эти биты, сдвигая их для расширения адресуемой памяти и реализации сжатых операций.

3. Сжатый упс в действии

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

Чтобы увидеть состояние кучи, мы используем Eclipse Memory Analyzer Tool. Поскольку мы не используем Eclipse IDE, мы используем отдельное приложение. Вы можете скачать его здесь .

Поскольку в этом примере используется только один класс, мы не используем Eclipse или любую другую IDE. Используйте текстовый редактор и создайте файл с именем IntegerApplication.java. Введите следующий код внутри файла. Помните, что имя файла должно совпадать с именем класса Java. Вместо того, чтобы вводить это вручную, вы можете скачать файл класса из раздела загрузки этой статьи.

IntegerApplication.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
 
public class IntegerApplication {
    public static void main(String[] args) {
        List<Integer> intList = new LinkedList<>();
        for(int i=0;i<2000000;i++){
            Integer number = new Integer(1);
            intList.add(number);
        }
        Scanner scanner = new Scanner(System.in);
        System.out.println("application is running...");
        String tmp = scanner.nextLine();
        System.exit(0);
    }
}

Откройте окно командной строки и перейдите к каталогу этого файла. Используйте следующую команду для его компиляции.

1
javac IntegerApplication.java

Теперь у вас должен быть файл IntegerApplication.class. Мы запускаем этот файл дважды, один раз с включенным сжатым опсом и второй раз без сжатого опса. Сжатый ой по умолчанию включен в JVM, более новых, чем 6u23, поэтому вам нужно только запустить приложение, введя это в командной строке:

1
java IntegerApplication

Возможно, вы заметили объект Scanner в исходном коде. Он используется для поддержки приложения, пока вы не наберете что-нибудь и не завершите его. Если вы видите в командной строке фразу «приложение работает…», пора запустить анализатор памяти. В зависимости от вашей машины процесс инициализации может занять некоторое время.

В меню «Файл» выберите «Получить дамп кучи».

Окно выбора процесса

Окно выбора процесса

вы увидите окно выбора процесса. Выберите процесс с именем IntegerApplication и нажмите «Готово».

Через некоторое время вы окажетесь на главном экране МАТ. На панели инструментов выберите кнопку гистограммы, как показано на рисунке:

Выберите гистограмму на панели инструментов

Выберите гистограмму на панели инструментов

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

Гистограмма приложения с включенными сжатыми опами.

Дамп кучи приложения с включенным сжатым опсом.

На этот раз мы запускаем приложение без сжатых упс. Чтобы отключить сжатые операции, мы используем флаг -XX: -UseCompressedOops. Вам не нужно перекомпилировать ваш класс снова, просто введите в командной строке следующую команду:

1
java -XX:-UseCompressedOops IntegerApplication

Опять же, когда вы видите текст «Приложение работает…», получите дамп кучи, как и раньше. Это гистограмма дампа кучи, когда приложение работает без сжатых сообщений.

Дамп кучи приложения с отключенным сжатым опсом

Дамп кучи приложения с отключенным сжатым опсом

Как мы и ожидали, размер кучи увеличился. Большую часть кучи занимают два типа объектов: узлы связанного списка и целые числа. Существует более 2 миллионов целых чисел, для которых в сжатой версии oops требовалось 32 миллиона байтов, а в несжатой версии oops 48 миллионов байтов. С простой математикой мы видим, что это в точности соответствует нашим прогнозам.

2000000 * (128/8) = 32000000 или 32 мегабайта

2000000 * (192/8) = 48000000 или 48 мегабайт

Если вы заметите, что во втором уравнении мы использовали 192, тогда как в приведенном выше разделе размер объекта был упомянут как 160 бит. Причина в том, что Java имеет байтовую адресацию, поэтому адрес выровнен с ближайшими 8 байтами, что в данном случае составляет 192 бита.

4. Вывод

Приведенный здесь пример был придуман, но это не значит, что он не применим в реальных приложениях. При тестировании с приложением базы данных H2 сжатые сообщения уменьшали размер кучи с 3,6 до 3,1 мегабайта. Это почти на 14% более эффективное использование ценного пространства кучи. Как мы видели, использование сжатых опций не повредит, и чаще всего вы не собираетесь отключать эту функцию. Но знание деталей хитростей компилятора может помочь в написании кодов с учетом производительности.

Скачать исходный код

Это был пример наблюдения эффекта сжатого упа в действии.

Скачать
Вы можете скачать полный код класса IntegerApplication, используемого в этом примере, здесь: IntegerApplication