← Назад к вопросам
Что произойдет при создании строки, занимающей много памяти, идентичной ранее созданной?
1.8 Middle🔥 61 комментариев
#JVM и управление памятью
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Создание идентичных строк и String Pool в Java
Этот вопрос касается одного из самых интересных и запутанных механизмов в Java — String Pool (String Interning). Когда мы создаём идентичные строки, Java использует специальную память для их оптимизации.
String Pool — что это такое?
// String Pool — это специальная область памяти в Java
// где хранятся уникальные строки
// Пример 1: Строки-литералы автоматически в Pool
String s1 = "hello"; // В Pool
String s2 = "hello"; // Указывает на ТО ЖЕ место в Pool
System.out.println(s1 == s2); // true! (одна и та же ссылка)
System.out.println(s1.equals(s2)); // true (по содержимому)
Где находится String Pool?
Java 8 и ранее:
┌─────────────────────┐
│ PermGen (Heap) │ <- String Pool (может привести к OutOfMemoryError)
└─────────────────────┘
Java 7:
┌─────────────────────┐
│ Heap │ <- String Pool переместился
└─────────────────────┘
Java 9+:
┌─────────────────────┐
│ Heap │ <- String Pool остаётся в Heap
└─────────────────────┘
Создание больших идентичных строк
public class LargeStringExample {
public static void main(String[] args) {
// Создаём большую строку-литерал
String large1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
+ "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
+ "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris...";
// Эта строка попадает в String Pool
// Создаём ИДЕНТИЧНУЮ строку
String large2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
+ "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
+ "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris...";
// ЧТО ПРОИЗОЙДЁТ:
System.out.println(large1 == large2); // true!
// Обе переменные указывают на ОДИН объект в String Pool
// Вторая строка не занимает новое место в памяти!
System.out.println(System.identityHashCode(large1));
System.out.println(System.identityHashCode(large2));
// Одинаковые хеши = один объект
}
}
Строки, созданные через new
public class NewStringExample {
public static void main(String[] args) {
// Создаём строку через new (НЕ в Pool)
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false! (разные объекты)
System.out.println(s1.equals(s2)); // true (одинаковое содержимое)
// Оба объекта в Heap, НЕ в String Pool
// Это тратит память впустую
// Литерал в Pool
String s3 = "hello";
String s4 = new String("hello");
System.out.println(s3 == s4); // false (разные места)
System.out.println(s3.equals(s4)); // true
}
}
Intern() — явное добавление в Pool
public class InternExample {
public static void main(String[] args) {
// Создаём через new (не в Pool)
String s1 = new String("hello");
// Добавляем в Pool явно
String s2 = s1.intern();
String s3 = "hello"; // литерал (в Pool)
System.out.println(s2 == s3); // true!
// s2 указывает на тот же объект в Pool, что и s3
// Но s1 всё равно отдельный объект
System.out.println(s1 == s3); // false
}
}
Большие строки и OutOfMemoryError
// ОПАСНО: может быть OutOfMemoryError в Java 8
public class OomExample {
public static void main(String[] args) {
// В Java 8 String Pool в PermGen (ограниченный размер)
// В Java 9+ в Heap (но всё равно может быть ошибка)
List<String> strings = new ArrayList<>();
// Создаём много больших строк
for (int i = 0; i < 1_000_000; i++) {
String huge = "x".repeat(100_000).intern();
strings.add(huge);
if (i % 100_000 == 0) {
System.out.println("Создано: " + i);
}
}
// Java 8: OutOfMemoryError: PermGen space
// Java 9+: OutOfMemoryError: Java heap space
}
}
Что происходит при создании большой строки?
public class DetailedFlow {
public static void main(String[] args) {
// 1. Создание большой строки-литерала
String s1 = "БОЛЬШАЯ_СТРОКА_ЛИТЕРАЛ_10000_СИМВОЛОВ";
// Под капотом:
// a) Компилятор обнаруживает строку-литерал
// b) При запуске JVM добавляет в String Pool
// c) s1 получает ссылку на объект в Pool
// d) Память: ~10000 байт в Pool
// 2. Создание идентичной строки-литерала
String s2 = "БОЛЬШАЯ_СТРОКА_ЛИТЕРАЛ_10000_СИМВОЛОВ";
// Под капотом:
// a) Компилятор снова обнаруживает этот литерал
// b) JVM проверяет Pool: строка уже есть!
// c) s2 получает ссылку на ТОТ ЖЕ объект
// d) Новая память НЕ выделяется!
// e) Экономия: ~10000 байт
System.out.println(s1 == s2); // true
// Это работает потому что s1 и s2 указывают на один объект
}
}
Конкатенация и String Pool
public class ConcatenationExample {
public static void main(String[] args) {
// Компилятор может оптимизировать
String s1 = "hello" + " " + "world";
// На этапе компиляции преобразуется в: "hello world"
// Это literal, поэтому в Pool
String s2 = "hello world";
System.out.println(s1 == s2); // true (оптимизация)
// Но конкатенация с переменными
String greeting = "hello";
String message = greeting + " world";
String literal = "hello world";
System.out.println(message == literal); // false!
// message создаётся в runtime, не попадает в Pool
// (кроме как если явно вызвать intern())
}
}
StringBuilder vs String для больших объёмов
// ПЛОХО: каждая конкатенация создаёт новый String
String result = "";
for (int i = 0; i < 10000; i++) {
result += "item" + i; // O(n) операция каждый раз
}
// Памяти потрачено: O(n^2)
// В Pool попадает каждая промежуточная строка!
// ХОРОШО: используй StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append("item").append(i);
}
String result = sb.toString(); // Одна String в конце
// Память: O(n), один раз в Pool
Размер String Pool
// Java 8:
// По умолчанию в PermGen, -XX:MaxPermSize управляет
// -XX:StringTableSize=66536 (по умолчанию)
// Java 9+:
// В Heap, настраивается через:
// -XX:StringTableSize=60013 (размер таблицы хеша)
public class CheckPoolSize {
public static void main(String[] args) {
// Нет прямого способа получить размер Pool
// Но можно оценить через System.gc()
Runtime runtime = Runtime.getRuntime();
long before = runtime.totalMemory() - runtime.freeMemory();
// Создаём много строк
for (int i = 0; i < 100000; i++) {
String s = new String(i).intern();
}
long after = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Память использована: " + (after - before));
}
}
Практические рекомендации
// 1. НЕ используй intern() для больших строк без надобности
String huge = largeString.intern(); // ПЛОХО
// 2. Используй intern() только если много повторений
List<String> keys = new ArrayList<>();
for (String key : hugeList) {
keys.add(key.intern()); // Экономит память если много дубликатов
}
// 3. Используй StringBuilder для конкатенации
StringBuilder sb = new StringBuilder();
sb.append(part1).append(part2).append(part3);
String result = sb.toString();
// 4. Осторожно с памятью при работе с большими строками
// Следи за String Pool size в production
Заключение
Когда мы создаём идентичные большие строки:
- Первая строка-литерал добавляется в String Pool
- Вторая идентичная строка-литерал НЕ дублируется — JVM использует ту же ссылку
- Экономия памяти — большая строка занимает место только один раз
- Через new String() строки НЕ автоматически в Pool
- Осторожно с intern() — может привести к утечке памяти
Это один из самых эффективных механизмов Java для оптимизации памяти при работе со строками.