Как работает String Pool?
В типичном Java-приложении строки составляют 25-40% всех объектов. Без пула каждая копия "Hello" создавала бы отдельный объект, даже если текст полностью идентичен.
🟢 Junior Level
String Pool (Пул строк) — это специальный механизм в JVM, который хранит только одну копию каждой уникальной строки. Это помогает экономить память.
В типичном Java-приложении строки составляют 25-40% всех объектов. Без пула каждая копия "Hello" создавала бы отдельный объект, даже если текст полностью идентичен.
Когда вы создаёте строку через литерал, JVM проверяет, есть ли уже такая строка в пуле. Если есть — возвращает ссылку на существующий объект. Если нет — создаёт новую и добавляет в пул.
Пример:
String s1 = "Hello";
String s2 = "Hello";
// s1 и s2 — это один и тот же объект в памяти!
System.out.println(s1 == s2); // true
Зачем это нужно: Если бы каждая строка создавалась как отдельный объект, приложение потребляло бы гораздо больше памяти, особенно если одинаковые строки встречаются часто.
Когда использовать: Используйте литералы строк (String s = "value";) по умолчанию — JVM сама поместит их в пул.
🟡 Middle Level
Как это работает
String Pool реализован как хеш-таблица StringTable внутри JVM. При загрузке класса все строковые литералы из константного пула класса (Constant Pool) автоматически помещаются в String Pool.
Constant Pool — таблица в .class-файле, содержащая все литералы, имена методов и другие константы класса.
Два пути попадания строки в пул:
- Литералы — автоматически при загрузке класса:
String s = "Hello"; - Метод intern() — вручную во время выполнения:
String s = new String("Hello").intern();
Практическое применение
String s1 = "Java"; // Литерал → сразу в пуле
String s2 = new String("Java"); // new → новый объект в куче (НЕ в пуле)
String s3 = s2.intern(); // intern() → возвращает ссылку из пула на s1
System.out.println(s1 == s2); // false (разные объекты)
System.out.println(s1 == s3); // true (один объект в пуле)
Типичные ошибки
-
Ошибка: Использование
new String("literal")без необходимости Решение: Используйте литералы —String s = "literal"; -
Ошибка: Сравнение строк через
==вместоequals()Решение: Всегда используйтеequals()для сравнения содержимого
Сравнение подходов
| Подход | В пуле? | Создаёт объект? | Когда использовать |
| ——————— | —————— | ———————- | ——————————————– |
| Литерал "Hello" | Да | Только если нет в пуле | 99% случаев |
| new String("Hello") | Нет (литерал — да) | Всегда новый | Практически никогда |
| s.intern() | Да | Только если нет в пуле | При работе с огромным количеством дубликатов |
🔴 Senior Level
Internal Implementation
String Pool — это нативная хеш-таблица StringTable с открытой адресацией (method chaining). Ключами и значениями являются ссылки на объекты java.lang.String.
// Упрощённая структура из OpenJDK
class StringTable : public RehashableHashtable<oop, mtSymbol> {
// oop — pointer to Java object
// Использует hashing через String::hash_code
};
Ключевые параметры JVM:
-XX:StringTableSize=N— размер хеш-таблицы (по умолчанию 60013 в Java 8+, ранее 1009)- Начиная с JDK 11 (JEP 341), StringTable поддерживает динамическое расширение (resize) при превышении load factor, аналогично HashMap.
Эволюция памяти String Pool
| Версия Java | Расположение | Проблемы |
|---|---|---|
| Java 6 и ниже | PermGen | Фиксированный размер, частые OOM: PermGen space |
| Java 7+ | Java Heap | Управляется GC, ограничен только -Xmx |
| Java 8+ (с Metaspace) | Java Heap (не Metaspace!) | Всё ещё может вызвать OOM: Java heap space |
Распространённое заблуждение: String Pool находится в Metaspace. Это неверно — он остался в Heap.
Архитектурные Trade-offs
Массовое использование intern():
Плюсы:
- Резкое сокращение потребления RAM при дублирующихся строках
- Меньше объектов → меньше пауз GC
Минусы:
intern()— нативный вызов с вычислением хеша и поиском в таблице- При большом количестве строк contention на глобальной StringTable
- Если
StringTableSizeмал → длинные цепочки коллизий → деградация O(n)
Edge Cases
-
Коллизии в StringTable: Если количество строк » StringTableSize, поиск превращается из O(1) в O(n). Проверьте:
jcmd <pid> VM.stringtable -verbose -
String Pool и GC: В Java 7+ строки из пула могут быть удалены GC, если на них нет ссылок. Но если вы удерживаете ссылки — они никогда не соберутся.
-
Compact Strings (Java 9+): Не влияют на механизм пула напрямую, но экономят 50% памяти для латинских строк внутри пула.
Производительность
- Lookup в пустом пуле: ~наносекунды
- Lookup в пуле с 1M строк (правильный StringTableSize): ~десятки наносекунд
- Lookup в пуле с 1M строк (малый StringTableSize): микросекунды (коллизии!)
Production Experience
При загрузке миллионов записей из БД (например, 1M пользователей с полем country), где уникальных стран всего 200:
- Без
intern(): 1M объектов String → ~48MB - С
intern(): 200 объектов в пуле + 1M ссылок → ~5MB - Но: overhead на CPU при каждом вызове
intern()может быть 10-50%
Monitoring
# Статистика StringTable
jcmd <pid> VM.stringtable -verbose
# Анализ через JOL
System.out.println(GraphLayout.parseInstance(stringTable).toFootprint());
Best Practices для Highload
- Увеличьте
-XX:StringTableSizeдо простого числа > количества ожидаемых уникальных строк - Не используйте
intern()для короткоживущих строк (умрут в Young Gen и так) - Рассмотрите
-XX:+UseStringDeduplication(G1 GC) как прозрачную альтернативу
🎯 Шпаргалка для интервью
Обязательно знать:
- String Pool — хеш-таблица (
StringTable) в JVM, хранит одну копию каждой уникальной строки - Литералы автоматически попадают в пул при загрузке класса
new String("...")создаёт отдельный объект в куче, НЕ в пулеintern()добавляет строку в пул и возвращает ссылку из пула- В Java 7+ String Pool находится в Java Heap (не в PermGen/Metaspace!)
-XX:StringTableSize— размер хеш-таблицы (по умолчанию 60013 в Java 8+)- При коллизиях в StringTable поиск деградирует из O(1) в O(n)
- Compact Strings (Java 9+) экономят 50% памяти для Latin-1 строк в пуле
Частые уточняющие вопросы:
- Где находится String Pool? — В Java Heap (начиная с Java 7). Распространённая ошибка — отвечать Metaspace.
- Как строка попадает в пул? — Через литералы (автоматически) или вызов
intern()(вручную). - Можно ли удалить строку из пула? — Да, в Java 7+ GC может собрать строки из пула, если на них нет ссылок.
- Что будет при
new String("Hello").intern()? — Создаётся объект в Heap, затемintern()находит"Hello"в пуле и возвращает ссылку из пула. Объект из Heap становится мусором.
Красные флаги (НЕ говорить):
- ❌ “String Pool находится в Metaspace” — неверно, он в Heap
- ❌ “
==всегда работает для строк” — работает только для литералов/интернированных строк - ❌ “
intern()бесплатный” — это нативный вызов с CPU overhead - ❌ “String Pool имеет фиксированный размер” — он ограничен только
-Xmxв Java 7+
Связанные темы:
- [[2. В чём разница между созданием String через литерал и через new]]
- [[3. Когда стоит использовать intern()]]
- [[11. Где хранится String Pool (в какой области памяти)]]
- [[12. Может ли String Pool вызвать OutOfMemoryError]]
- [[22. Что такое String deduplication в G1 GC]]