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

Для чего нужен пул примитивов java

  • автор:

Пул констант

Многие знают, что в каждом .class-файле есть замечательная структура данных, которая называется пулом констант. Но далеко не каждый Java-разработчик, глядя на исходник, сможет даже примерно оценить, сколько констант будет создано в пуле.

Возьмём, к примеру, такой код:

System.out.println("Hello World!");

Он транслируется в три инструкции байткода: getstatic (для загрузки статического поля System.out), ldc (для загрузки константной строки «Hello World!») и invokevirtual (для выполнения виртуальной функции println). Попробуйте прикинуть, сколько констант нужно для того, чтобы этот код работал.

Оказывается, эта строка использует 14 констант, которые схематично можно изобразить таким образом:

Как мы видим, большинство констант являются ссылками на другие константы. У каждой константы есть тип, значение и номер, по которому на неё можно сослаться. Вот базовые типы, которые ни на что не ссылаются:

  • Integer — целое число. На них ссылаются из кода, там, где число потребовалось. Числа от -32768 до 32767 компилятор сюда не складывает: их можно явно указать в коде с помощью инструкций iconst_x, bipush и sipush.
  • Long — длинное целое. По историческим причинам занимает две позиции в пуле констант (то есть у следующей константы номер на два больше). Сэкономить можно только на 0L и 1L с помощью lconst_x; 2L уже попадает в пул.
  • Float — вещественное одинарной точности. Специальные инструкции fconst_x есть для 0.0f, 1.0f и 2.0f; остальные используемые числа попадают в пул.
  • Double — вещественное двойной точности. Как и Long, занимает две позиции в пуле. Только числа 0.0 и 1.0 можно использовать без пула с помощью инструкций dconst_x.
  • Utf8 — строковой литерал в кодировке Utf8. Максимальная длина не превышает 65535 байт (символов, конечно, может быть меньше). Как раз на эти литералы ссылаются из других констант чаще всего.

А вот некоторые из ссылочных типов:

  • String — строка. Содержит ссылку на константу типа Utf8. Если вы используете в коде строку, вы ссылаетесь на константу типа String. Выглядит как лишняя сущность, но есть.
  • NameAndType — константа, описывающая имя и тип поля или метода. Содержит две ссылки на Utf8-константы с именем и типом.
  • Class — константа, описывающая имя класса или интерфейса. Содержит ссылку на Utf8-константу с внутренним именем класса.
  • Fieldref — константа, описывающая конкретное поле конкретного класса. Содержит ссылку на Class-константу и ссылку на NameAndType-константу.
  • MethodRef, InterfaceMethodRef — константы, описывающие конкретный метод класса или интерфейса. Содержат ссылки на Class-константу и NameAndType-константу.

Для обращения к полю вам нужно не только его имя, но и полное имя класса, где поле объявлено, а также тип поля. Для обращения к методу вам нужен класс, имя и сигнатура метода. К счастью, вся сигнатура кодируется в одну строку вне зависимости от количества параметров метода: примитивные типы кодируются одной буквой (например, D = double), объекты — буквой L, за которой следует полное имя класса и точка с запятой, а один уровень массива добавляет квадратную скобку. В скобочках идут типы аргументов, а за ними — возвращаемый тип (V — это void). Например, сигнатура (IDLjava/lang/Thread;)Ljava/lang/Object; будет у такого метода:

Object m(int i, double d, Thread t)

От того, что для ссылки на метод требуется его точная сигнатура, разработчики иногда напарываются на NoSuchMethodError. Скажем, вы поменяли возвращаемый тип метода на более конкретный. Тогда вызовы этого метода из других классов остаются прежними, и компилятор не будет пересобирать эти классы, потому что исходные файлы не изменились. Однако при попытке вызвать этот метод во время выполнения, Java-машина будет искать старую сигнатуру.

Если вы автоматически генерируете Java-код, помните, что максимальный номер константы в пуле не превышает 65535, после этого будет ошибка компиляции. Этого не такое большое число, учитывая огромное количество ссылок и то, что long и double занимают по две позиции. Знание устройства пула поможет вам контролировать его заполнение при генерации кода.

Подробнее с устройством пула констант можно ознакомиться в §4.4 спецификации виртуальной машины Java.

String Pool в Java

Java даёт выбор между примитивными типами данных и объектными. Одни передаются по значению, другие по ссылке. Одни занимают предсказуемое количество памяти, другие не очень (конечно только если Вы не знаете размеры метаинформации класса, для которого хотите произвести расчёт). Под одних память выделяется на стеке, под другие в heap’е. Они сильно отличаются друг от друга

Элементы в пределах своего типа (примитивный или объектный) ведут себя похоже, независимо от конкретного типа данных. Значения int ведут себя так же, как и значения типа short. В объектных типах данных схожая ситуация. Но есть исключения. Например — объектный тип String.

Что такое String?

String — это класс в Java, то есть объектный тип. Он описывает строки и хранит их данные в массиве char.

Оговорка:
Тип char используется в старых версиях Java, например 8-ой. В Java 11 используется уже массив byte’ов.

Сколько памяти занимает String? Примитивный тип char в Java имеет размер 2 byte’а. То есть один символ занимает в памяти 2 байта. Теперь представьте — каждый раз когда мы используем строку в Java, будь-то имя пользователя или ссылку на какой-либо сайт, мы создаём в системе большой массив char. Это занимает память. В объектных типах помимо всех ссылок на объекты и примитивов, память занимает ещё и заголовочная информация класса.

Зачем это знать? Строки — самый популярный тип данных в Java. Огромное количество данных описывается строками. Ещё более интересн тот факт, что строки в одних и тех же программах часто повторяются.

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

Создатели Java заранее позаботились об этой проблеме и сделали тип String не совсем обычным объектным типом.

Что такое строковый пул?

Строковый пул или String pool — это особое место в heap’е, куда попадают объекты типа String после их создания. Он выполняет функцию кеша строк. Каждый раз, когда Вы создаёте строку, она попадает в строковый пул. Если же на момент создания новой строки пул уже содержит такое же значение, то вместо создания нового объекта возвращается тот, что уже лежит в пуле.

У Вас есть возможность влиять на это поведение и не класть объекты в пул, если требуется.

Как работать со строковым пулом?

Разберёмся с тем как работает String pool на практике.

Обычно строки в Java программах объявляются так:

String text = “Hello World”;

Что происходит в JVM за кадром? Остановитесь и подумайте. Если Вашим ответом будет что-то вроде — «В heap’е будет выделена память под строковый объект и ссылка на него будет возвращена и присвоена локальной переменной text» — Вы правы.

String text = “Hello World”;
String text2 = “Hello World”;

А что произойдёт тут? Если Вы думаете, что на обе локальные переменные будет выделена память в heap’е — Вы ошибаетесь. Именно в этом примере и видно результат работы пула строк.

В Java все строки, объявленные в виде литералов, то есть так:

“My String”

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

Как убедиться в том, действительно ли обе переменные указывают на один и тот же объект? Очень просто. Достаточно посмотреть что будет выведено в результате следующей операции:

System.out.println(text == text2);

Не спешите набирать код, я Вам подскажу — ответом будет `true`. Это означает — обе переменные указывают на один и тот же объект.

А как тогда сделать так, чтобы строки в пул не попадали? Это тоже сделать просто. Используйте конструктор явно при создании строк. Вот так:

String text = new String(“text”);

В этом случае, объект типа String будет создан, память под него будет выделена в heap’е, но в строковый пул он не попадёт. Это легко проверяется следующим примером:

String text = “Hello World”;
String text2 = new String(“Hello World”);
System.out.println(text == text2);

Результатом работы кода выше будет `false`, потому что теперь ссылки указывают на два разных объекта.

Ну и наконец, как добавить строку в строковый пул после её создания? Для этого класс String содержит метод под названием `intern()`. Именно он отвечает за сохранение текущего объекта String в пул строк. Пример использования:

String text = “Hello World”;
String text2 = new String(“Hello World”);
System.out.println(text == text2); // выведет false
text2 = text2.intern(); // попытается положить текущую строку в пул строк, обнаружит что такое значение уже там есть и вернёт объект из строкового пула. То есть тот, на который указывает переменная text
System.out.println(text == text2); // выведет true

Зачем знать о строковом пуле?

Строковый пул несёт не только пользу. Если не знать о его существовании и принципах работы, можно легко получить дыру в безопасности приложения. Сделать это довольно просто — достаточно добавить в пул какой-нибудь пароль или логин. Содержимое строкового пула доступно в memory dump’ах, к которым Вы или кто-то другой может получить доступ.

Надеюсь эта статья поможет Вам писать код более осознанно, ведь, теперь Вы знаете, что за строками в Java стоит String pool.

Для чего нужен пул примитивов java

Наверняка вам известно, что при сравнении двух объектов в Java на == обычно получается не тот результат, что нужен, т.к. идет сравнение ссылок на объекты, а не данных этих объектов.

2015-02-19T10:02 19 фев 2015 10:02
Просмотров: 11176

Наверняка вам известно, что при сравнении двух объектов в Java на == обычно получается не тот результат, что нужен, т.к. идет сравнение ссылок на объекты, а не данных этих объектов.

Хм, давайте тогда посмотрим на следующий код и на результат его выполнения:

public class Temp < public static void main(String []args)< Integer i1 = 10; Integer i2 = 10; System.out.println(i1 == i2); >>

Как думаете, что он напечатает? А напечатает он true.

А вот еще один код:

public class Temp < public static void main(String []args)< Integer i1 = 130; Integer i2 = 130; System.out.println(i1 == i2); >>

Этот код уже выведет false.

На первый взгляд странно, не так ли? Сейчас объясню.

В Java есть пул(pool) целых чисел в промежутке [-128;127]. Т.е. если мы создаем Integer в этом промежутке, то вместо того, чтобы каждый раз создавать новый объект, JVM берет их из пула. Таким образом, в первом примере i1 и i2 указывают на один и тот же объект из пула, а во втором — создаются два разных объекта. Отсюда и результат. Стоит заметить, что в приведенных выше примерах не использовалось ключевое слово new.

public class Temp < public static void main(String []args)< Integer i1 = new Integer(10); Integer i2 = new Integer(10); System.out.println(i1 == i2); >>

Результатом будет false. Тут мы уже явно говорим, что хотим создать новый(new) объект, так что ссылки разные.

Кеширование или Integer пул?

Как работает кеширование при операции autoboxing? Начиная с Java 1.5, для целочисленных значений в диапазоне от -128 до + 127 объекты-обёртки кешируются внутри для повторного использования. В классе-обёртке Integer есть внутренний класс IntegerCache. Он объявлен как private static. В этом внутреннем классе кешированные объекты находятся в массиве cache[]. Кеширование выполняется при первом использовании класса-обёртки. После первого использования, вместо создания нового экземпляра (кроме использования конструктора), используются кешированные объекты. Код метода valueOf() класса Integer выгдядит так: Кэширование касается не только класса-оболочки Integer. Имеются аналогичные реализации кеширования для других классов-оболочек целочисленных типов: ByteCache, ShortCache, LongCache, CharacterCache. Кешированные объекты не используются при создании объекта-обёртки с помощью конструктора. На хабре есть статья, в которой SSiarhei (предполагаю, что крутой программист, потому что сдавал экзамен Oracle Certified Professional Java Programmer), в 2011 году написал: https://habr.com/ru/post/111189/ Так что происходит на самом деле? Есть ли Integer пул, и если да, то какую роль он играет?

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

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