Як працює String pool і як це пов'язано з незмінністю?
Якби рядки можна було змінювати, то зміна одного рядка вплинула б на всі інші, які посилаються на нього:
Junior Level
String Pool — це спеціальне сховище в пам’яті, де Java зберігає лише одну копію кожного унікального рядка.
String a = "Hello";
String b = "Hello";
System.out.println(a == b); // true — один і той самий рядок з пулу
Чому це працює тільки з незмінними рядками
Якби рядки можна було змінювати, то зміна одного рядка вплинула б на всі інші, які посилаються на нього:
// Гіпотетично — якби рядки були мутабельними
String a = "Hello";
String b = "Hello"; // те ж посилання з пулу
a.changeTo("World"); // зламає b теж!
Метод intern()
String s1 = new String("Hello"); // новий об'єкт в heap
String s2 = s1.intern(); // додає в пул, повертає посилання
String s3 = "Hello"; // з пулу
System.out.println(s2 == s3); // true
Middle Level
Механізм роботи
- При компіляції — усі рядкові літерали потрапляють до таблиці констант класу
- При завантаженні — JVM перевіряє пул: якщо рядок є — повертає посилання, ні — створює новий
- Під час виконання —
intern()додає рядок з heap в пул
Еволюція пам’яті
- Java 6: String Pool був у PermGen (обмежений розмір, часті OOM)
- Java 7u6+: String Pool переїхав з PermGen у звичайний Heap (керується GC)
Переваги
- Економія пам’яті — дублікати рядків не створюються
- Швидке порівняння — інтерновані рядки можна порівнювати через
==за O(1) (одна інструкція CPU — порівняння посилань), тоді якequals()проходить по всіх символах. - Кешування hashCode — обчислюється один раз
Коли використовувати intern()
- Багато повторюваних рядків (ключі, константи конфігурації)
- Ключі в
HashMapз мільярдами записів (економія на дублікатах)
Senior Level
Зв’язок з незмінністю
Незмінність — необхідна умова для String Pool. Патерн Flyweight на рівні мови працює лише тому, що Java гарантує: вміст String ніколи не зміниться.
Продуктивність
- Порівняння через
==для інтернованих рядків — O(1), але в бізнес-логіці все одно використовуйтеequals() - Кешований
hashCodeприскорює роботуHashMap— повторні lookup’и не вимагають перерахунку
Ризики intern()
- Memory leak — рядки з пулу не видаляються GC, доки живий ClassLoader
- PermGen/Heap OOM — занадто багато унікальних рядків через
intern()може переповнити пам’ять - В Java 7+ пул в Heap, але рядки все ще можуть жити дуже довго
Резюме для Senior
- String Pool — Flyweight патерн на рівні мови
- Працює лише завдяки гарантії незмінності
- Пул в Heap (Java 7+), керується GC
intern()— потужний, але небезпечний інструмент: слідкуйте за обсягом унікальних рядків
Шпаргалка для інтерв’ю
Обов’язково знати:
- String Pool — сховище унікальних рядків, працює ТІЛЬКИ завдяки незмінності (Flyweight)
- Літерали потрапляють в пул при завантаженні класу,
intern()додає рядок з heap - Java 6: пул в PermGen (часті OOM); Java 7+: пул в Heap (керується GC)
- Порівняння інтернованих рядків через
==— O(1), але в бізнес-логізі використовуйтеequals() - String кешує hashCode — обчислюється один раз, прискорює HashMap
- Ризики intern(): memory leak, Heap OOM при занадто великій кількості унікальних рядків
Часті уточнювальні запитання:
- Чому пул неможливий без незмінності? — Зміна одного рядка зламає всі посилання на нього
- Коли використовувати intern()? — Багато повторюваних рядків, ключі в величезних HashMap
- intern() — це безпечно? — Рядки з пулу не видаляються GC, доки живий ClassLoader — risk of OOM
- Compact Strings впливають на пул? — Ні, пул працює незалежно; Compact Strings економлять 50% пам’яті
Червоні прапорці (НЕ говорити):
- «String Pool в PermGen» — з Java 7+ в Heap
- «intern() завжди прискорює» — при зловживанні викликає OOM
- «Порівнювати рядки через == нормально після intern()» — в бізнес-логізі завжди equals()
- «Пул копіює рядки» — літерали поміщаються в пул при завантаженні класу
Пов’язані теми:
- [[4. Чому клас String є незмінним]]
- [[5. Які наслідки незмінності String]]
- [[19. Чи можна змінити значення String через рефлексію]]
- [[27. Чи можна використовувати незмінні об’єкти як ключі в HashMap]]