Что такое пул строк java
Перейти к содержимому

Что такое пул строк java

  • автор:

Руководство по пулу строк Java

Объект String является наиболее часто используемым классом в языке Java.

В этой быстрой статье мы рассмотрим пул строк Java — специальную область памяти, в которой JVM хранит строки .

2. Стажировка строк

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

Когда мы создаем переменную String и присваиваем ей значение, JVM ищет в пуле строку с равным значением.

Если он найден, компилятор Java просто вернет ссылку на свой адрес памяти, не выделяя дополнительной памяти.

Если он не найден, он будет добавлен в пул (интернирован), и его ссылка будет возвращена.

Давайте напишем небольшой тест, чтобы проверить это:

 String constantString1 = "ForEach";   String constantString2 = "ForEach";    assertThat(constantString1)   .isSameAs(constantString2); 

3. Строки , выделенные с помощью конструктора

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

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

Давайте посмотрим, чем это отличается от предыдущего случая:

 String constantString = "ForEach";   String newString = new String("ForEach");    assertThat(constantString).isNotSameAs(newString); 

4. Строковый литерал против строкового объекта

Когда мы создаем объект String с помощью оператора new() , он всегда создает новый объект в куче памяти. С другой стороны, если мы создадим объект, используя синтаксис строкового литерала, например, « ForEach », он может вернуть существующий объект из пула строк, если он уже существует. В противном случае он создаст новый объект String и поместит его в пул строк для повторного использования в будущем.

На высоком уровне оба являются объектами String , но основное различие заключается в том, что оператор new() всегда создает новый объект String . Кроме того, когда мы создаем строку , используя литерал, она интернируется.

Это станет намного яснее, если мы сравним два объекта String , созданные с использованием литерала String и оператора new :

 String first = "ForEach";   String second = "ForEach";   System.out.println(first == second); // True 

В этом примере объекты String будут иметь одну и ту же ссылку.

Далее создадим два разных объекта с помощью new и проверим, что у них разные ссылки:

 String third = new String("ForEach");   String fourth = new String("ForEach");   System.out.println(third == fourth); // False 

Точно так же, когда мы сравниваем литерал String с объектом String, созданным с помощью оператора new() с помощью оператора ==, он вернет false:

 String fifth = "ForEach";   String sixth = new String("ForEach");   System.out.println(fifth == sixth); // False 

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

5. Стажировка вручную

Мы можем вручную интернировать строку в пуле строк Java, вызвав метод intern() для объекта, который мы хотим интернировать.

Интернирование строки вручную сохранит ее ссылку в пуле, и JVM вернет эту ссылку при необходимости.

Давайте создадим тестовый пример для этого:

 String constantString = "interned ForEach";   String newString = new String("interned ForEach");    assertThat(constantString).isNotSameAs(newString);    String internedString = newString.intern();    assertThat(constantString)   .isSameAs(internedString); 

6. Сбор мусора

До Java 7 JVM помещала Java String Pool в пространство PermGen , которое имеет фиксированный размер — его нельзя расширить во время выполнения и нельзя использовать для сборки мусора .

Риск интернирования Strings в PermGen (вместо Heap ) заключается в том, что мы можем получить ошибку OutOfMemory от JVM, если интернируем слишком много Strings .

Начиная с Java 7, пул строк Java хранится в пространстве кучи , которое является мусором , собираемым JVM . Преимущество этого подхода заключается в снижении риска ошибки OutOfMemory , поскольку строки , на которые нет ссылок , будут удалены из пула, тем самым освобождая память.

7. Производительность и оптимизация

В Java 6 единственная оптимизация, которую мы можем выполнить, — это увеличение пространства PermGen во время вызова программы с параметром JVM MaxPermSize :

 -XX:MaxPermSize=1G 

В Java 7 у нас есть более подробные параметры для проверки и расширения/уменьшения размера пула. Рассмотрим два варианта просмотра размера пула:

 -XX:+PrintFlagsFinal 
 -XX:+PrintStringTableStatistics 

Если мы хотим увеличить размер пула с точки зрения сегментов, мы можем использовать параметр StringTableSize JVM:

 -XX:StringTableSize=4901 

До Java 7u40 размер пула по умолчанию составлял 1009 сегментов, но в более поздних версиях Java это значение претерпело некоторые изменения. Если быть точным, размер пула по умолчанию от Java 7u40 до Java 11 составлял 60013, а теперь он увеличился до 65536.

Обратите внимание, что увеличение размера пула потребует больше памяти, но имеет то преимущество, что сокращает время, необходимое для вставки строк в таблицу.

8. Примечание о Java 9

До Java 8 строки были внутренне представлены как массив символов — char[] , закодированный в UTF-16 , так что каждый символ использует два байта памяти.

В Java 9 предоставляется новое представление, называемое компактными строками. Этот новый формат выберет подходящую кодировку между char[] и byte[] в зависимости от сохраненного содержимого.

Поскольку новое представление String будет использовать кодировку UTF-16 только при необходимости, объем памяти кучи будет значительно меньше, что, в свою очередь, приведет к меньшим накладным расходам сборщика мусора на JVM.

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

В этом руководстве мы показали, как JVM и компилятор Java оптимизируют выделение памяти для объектов String через Java String Pool.

Все примеры кода, использованные в статье, доступны на GitHub .

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.

Главная

Пул строк — это конкретная реализация JVM концепции string interning.

Когда мы используем двойные кавычки для создания строки, сначала ищется строка в пуле с таким же значением, если находится, то просто возвращается ссылка, иначе создается новая строка в пуле, а затем возвращается ссылка .
Пул строк возможен исключительно благодаря неизменяемости строк в Java и реализации идеи интернирования строк. Пул строк также является примером паттерна Приспособленец (Flyweight).
когда мы используем оператор new , мы принуждаем класс String создать новый объект строки, а затем мы можем использовать метод intern() для того, чтобы поместить строку в пул, или получить из пула ссылку на другой объект String с таким же значением.

Когда метод intern() вызван, если пул строк уже содержит строку, эквивалентную к нашему объекту, что подтверждается методом equals (Object), тогда возвращается ссылка на строку из пула. В противном случае объект строки добавляется в пул и ссылка на этот объект возвращается.

Сборка мусора :

До Java 7 , пул Java String помещался в пространство PermGen , которое имеет фиксированный размер — его нельзя развернуть во время выполнения и не подходит для сборки мусора .
Риск интернирования строк в PermGen (вместо кучи ) заключается в том, что мы можем получить ошибку OutOfMemory от JVM, если интернируем слишком много строк .

Начиная с Java 7, пул Java String хранится в пространстве Heap , собираемым JVM . Преимущество этого подхода заключается в уменьшении риска ошибки OutOfMemory, поскольку строки, на которые нет ссылок, будут удалены из пула, тем самым освобождая память.

работа пула строк :
 public class MyStringPool < public static void main(String[] args) < String s1 = "Cat"; // создаем строку и помещаем ее в pool строк String s2 = "Cat"; // создаем строку и помещаем ее в pool строк String s3 = new String("Cat"); // создаем новый обьект (не помещаем его в pool) System.out.println("s1 == s2 :"+(s1==s2)); System.out.println("s1 == s3 :"+(s1==s3)); > > s1 == s2 :true s1 == s3 :false 
*.intern() :
 String str1 = "first"; String str2 = "first"; String str3 = new String("first"); str3 = str3.intern(); System.out.println(str1 == str2); System.out.println(str2 == str3); System.out.println(str1 == str3); true true true 
Полезные ссылки:
  • «str».intern() in JavaDoc (Java 8)
  • «str».intern() in JavaDoc (Java 7)
  • «str».intern() in JavaDoc (Java 6)

По каким критериям в Java строка попадает в пул строк?

По сути этот вопрос продолжает другой вопрос. Во многих статьях говорится, что при создании строк без new через литерал строка попадает в пул строк. В противном случае необходим метод intern . Как тогда объяснить поведение ниже ?

 String s2 = "hello"; String s1 = "hello"; System.out.println(s1 == s2); // true System.out.println("hel" + "lo" == "hello"); // true s1 = "hello"; s2 = "hel"; String s3 = "lo"; System.out.println(s1 == s2 + s3); // false System.out.println(s2 + s3 == "hel" + "lo"); //false 

Если в последнем случае s2 ссылается туда же куда и литерал «hel», и с s3 такая же история, то почему «hel» + «lo» не равно (по ссылке) s2 + s3 ?

Отслеживать
задан 6 сен 2017 в 9:12
7,738 17 17 золотых знаков 74 74 серебряных знака 134 134 бронзовых знака

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

Строка попадет в пул строк только после вызова метода intern класса String.

Детально про строковые литералы из спецификации на английском

По поводу «hel» + «lo» — это строковая константа

 public static void main(String[] args)

Скомпилируем и посмотрим его декомпилятором

public static void main(String[] args)

Компилятор собрал «hel» + «lo» в одну строку, что бы доказать это, посмотрим байткод:

public static main([Ljava/lang/String;)V L0 LINENUMBER 6 L0 LDC "hel" ASTORE 1 L1 LINENUMBER 7 L1 LDC "lo" ASTORE 2 L2 LINENUMBER 9 L2 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder. ()V ALOAD 1 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ALOAD 2 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; LDC "hello" IF_ACMPNE L3 ICONST_1 GOTO L4 L3 FRAME FULL [[Ljava/lang/String; java/lang/String java/lang/String] [java/io/PrintStream] ICONST_0 L4 FRAME FULL [[Ljava/lang/String; java/lang/String java/lang/String] [java/io/PrintStream I] INVOKEVIRTUAL java/io/PrintStream.println (Z)V L5 LINENUMBER 10 L5 RETURN L6 LOCALVARIABLE args [Ljava/lang/String; L0 L6 0 LOCALVARIABLE s2 Ljava/lang/String; L1 L6 1 LOCALVARIABLE s3 Ljava/lang/String; L2 L6 2 MAXSTACK = 3 MAXLOCALS = 3 > 

«hello» представлена константой, s2 + s3 собираются через StringBuilder и преобразуются в строку через toString(), соответственно это 2 разных объекта, и при сравнении указателей будет false

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

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