1. Обзор
Объект String является наиболее часто используемым классом в языке Java.
В этой краткой статье мы рассмотрим пул строк Java — специальную область памяти, где Строки хранятся в JVM .
2. Интернирование строк
Благодаря неизменяемости Строк в Java JVM может оптимизировать объем выделенной для них памяти, храня только одну копию каждого литерала Строки в пуле . Этот процесс называется интернированием .
Когда мы создаем переменную String и присваиваем ей значение, JVM ищет в пуле String равного значения.
Если он будет найден, компилятор Java просто вернет ссылку на свой адрес памяти, не выделяя дополнительной памяти.
Если он не найден, он будет добавлен в пул (интернет), и его ссылка будет возвращена.
Давайте напишем небольшой тест, чтобы проверить это:
String constantString1 = "Baeldung"; String constantString2 = "Baeldung"; assertThat(constantString1) .isSameAs(constantString2);
3. Строки, выделенные с помощью конструктора
Когда мы создаем Строку с помощью оператора new , компилятор Java создаст новый объект и сохранит его в пространстве кучи, зарезервированном для JVM.
Каждая строка |, созданная подобным образом, будет указывать на другую область памяти со своим собственным адресом.
Давайте посмотрим, чем это отличается от предыдущего случая:
String constantString = "Baeldung"; String newString = new String("Baeldung"); assertThat(constantString).isNotSameAs(newString);
4. Строковый литерал против строкового объекта
Когда мы создаем объект String с помощью оператора new () , он всегда создает новый объект в памяти кучи. С другой стороны, если мы создадим объект с использованием синтаксиса String literal, например “Baeldung”, он может вернуть существующий объект из пула строк, если он уже существует. В противном случае он создаст новый строковый объект и поместит его в пул строк для последующего повторного использования.
На высоком уровне оба являются объектами String , но основное различие заключается в том, что оператор new() всегда создает новый объект String . Кроме того, когда мы создаем Строку с помощью литерала – он интернируется.
Это будет гораздо более ясно, когда мы сравним два объекта String , созданных с помощью String литерала и нового оператора:
String first = "Baeldung"; String second = "Baeldung"; System.out.println(first == second); // True
В этом примере объекты String будут иметь одну и ту же ссылку.
Затем давайте создадим два разных объекта с помощью new и проверим, что у них разные ссылки:
String third = new String("Baeldung"); String fourth = new String("Baeldung"); System.out.println(third == fourth); // False
Аналогично, когда мы сравниваем литерал String с объектом String , созданным с помощью оператора new () , он возвращает false:
String fifth = "Baeldung"; String sixth = new String("Baeldung"); System.out.println(fifth == sixth); // False
В общем, мы должны использовать строковое литеральное обозначение, когда это возможно . Это легче читать, и это дает компилятору возможность оптимизировать наш код.
5. Ручная Стажировка
Мы можем вручную ввести String в пул строк Java, вызвав метод intern() для объекта, который мы хотим интернировать.
Ручное интернирование строки | сохранит ее ссылку в пуле, и JVM вернет эту ссылку при необходимости.
Давайте создадим тестовый случай для этого:
String constantString = "interned Baeldung"; String newString = new String("interned Baeldung"); assertThat(constantString).isNotSameAs(newString); String internedString = newString.intern(); assertThat(constantString) .isSameAs(internedString);
6. Сбор Мусора
До Java 7 JVM помещала пул строк Java в пространство PermGen , которое имеет фиксированный размер — оно не может быть расширено во время выполнения и не подходит для сборки мусора .
Риск интернирования Строк в PermGen (вместо кучи ) заключается в том, что мы можем получить OutOfMemory ошибку из JVM, если мы интернируем слишком много строк .
Начиная с Java 7, пул строк Java хранится в пространстве Heap , которое является мусором, собранным JVM . Преимуществом этого подхода является снижение риска OutOfMemory ошибки , поскольку не связанные Строки будут удалены из пула, тем самым освободив память.
7. Производительность и оптимизация
В Java 6 единственная оптимизация, которую мы можем выполнить, – это увеличение пространства PermGen во время вызова программы с помощью параметра MaxPermSize JVM:
-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 предоставляется новое представление, называемое Compact Strings. Этот новый формат будет выбирать соответствующую кодировку между char[] и byte[] в зависимости от сохраненного содержимого.
Поскольку новое представление String будет использовать кодировку UTF-16 только в случае необходимости, объем кучи памяти будет значительно ниже, что, в свою очередь, приведет к меньшим затратам Сборщика мусора на JVM.
9. Заключение
В этом руководстве мы показали, как JVM и компилятор Java оптимизируют выделение памяти для объектов String через пул строк Java.
Все примеры кода, используемые в статье, доступны на GitHub .