← Назад к вопросам

Что произойдет при создании строки, занимающей много памяти, идентичной ранее созданной?

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

Заключение

Когда мы создаём идентичные большие строки:

  1. Первая строка-литерал добавляется в String Pool
  2. Вторая идентичная строка-литерал НЕ дублируется — JVM использует ту же ссылку
  3. Экономия памяти — большая строка занимает место только один раз
  4. Через new String() строки НЕ автоматически в Pool
  5. Осторожно с intern() — может привести к утечке памяти

Это один из самых эффективных механизмов Java для оптимизации памяти при работе со строками.