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

Что такое Pool строк?

2.3 Middle🔥 151 комментариев
#SOLID и паттерны проектирования#Spring Boot и Spring Data

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

String Pool в Java: оптимизация памяти для строк

String Pool — это **специальное место в памяти Java (Heap)**, где хранятся уникальные строковые значения. Это механизм оптимизации, который избегает дублирования одинаковых строк в памяти и улучшает производительность.

Основная идея

// Без Pool (гипотетически): две переменные, две разные строки в памяти
String s1 = "hello";  // heap: создается объект "hello"
String s2 = "hello";  // heap: создается НОВЫЙ объект "hello"
// Память потрачена впустую на дублик!

// С Pool: обе переменные указывают на один объект
String s1 = "hello";  // heap: создается объект "hello" и добавляется в Pool
String s2 = "hello";  // Pool: находит существующий объект, реиспользует его
// s1 == s2 // true — это один и тот же объект!

Где находится Pool?

В Java 7+ String Pool находится на Heap (раньше в Java 6 был в PermGen).

Heap (Runtime Data Area)
├── Eden (молодое поколение)
├── Survivor Spaces
├── Old Generation
└── String Pool (часть Heap)

Литеральные строки vs String объекты

1. Строковые литералы — в Pool

// ✅ Эта строка будет в Pool
String s1 = "hello";
// Pool: ["hello"]

String s2 = "hello";
// s1 == s2 // true (один и тот же объект)
// Pool: ["hello"] — не добавилась новая

String s3 = "hello";
// s1 == s3 // true
// Pool: ["hello"] — по-прежнему одна

2. String объекты (new) — НЕ в Pool автоматически

// ❌ Эта строка НЕ будет в Pool
String s1 = new String("hello");
// Pool: ["hello"] — литеральное значение
// Heap: отдельный объект new String("hello")

String s2 = new String("hello");
// s1 == s2 // false! (два разных объекта в памяти)

String s3 = "hello";
// s1 == s3 // false (new String != литеральная строка)
// но s1.equals(s3) // true (значения одинаковые)

Визуализация памяти

Пример: две переменные, одна значение

String s1 = "hello";    // литеральная
String s2 = "hello";    // литеральная
String s3 = new String("hello");  // через new

Heap:
┌──────────────────────────────────┐
│         String Pool              │
│  ┌────────────────────────────┐  │
│  │ "hello" (один объект)      │  │
│  └────────────────────────────┘  │
│           ↑            ↑          │
│           │            │          │
│          s1           s2          │
│                                   │
│  ┌────────────────────────────┐  │
│  │ new String("hello")        │  │  ← отдельный объект
│  └────────────────────────────┘  │
│           ↑                       │
│           │                       │
│          s3                       │
└──────────────────────────────────┘

s1 == s2  // true (указывают на один объект в Pool)
s1 == s3  // false (s3 — отдельный объект)
s1.equals(s3)  // true (значения одинаковые)

Метод intern()

Если нужно добавить строку в Pool вручную, используется intern():

String s1 = new String("hello");  // НЕ в Pool
String s2 = s1.intern();           // добавляет в Pool и возвращает ссылку
String s3 = "hello";               // литеральная, в Pool

s2 == s3  // true! (оба в Pool)

// intern() работает так:
// 1. Ищет строку в Pool
// 2. Если находит — возвращает существующий объект
// 3. Если не находит — добавляет текущую и возвращает её

Пример с intern() в реальном коде

public class StringPoolExample {
    public static void main(String[] args) {
        // Создаем строку из внешних данных (например, БД)
        String country = new String("USA");
        
        // Без intern() — новый объект каждый раз
        if (country.equals("USA")) {  // equals работает
            System.out.println("American user");
        }
        
        // Если использовать intern()
        String countryPooled = country.intern();
        if (countryPooled == "USA") {  // == работает! (быстрее)
            System.out.println("American user");
        }
    }
}

Когда строка добавляется в Pool

// ✅ В Pool добавляются:

// 1. Строковые литералы
String s = "hello";

// 2. Результат String.intern()
String s = new String("hello").intern();

// 3. Строки из .concat(), если оба аргумента литеральные
String s = "hello" + " world";  // может быть в Pool (зависит от JVM)

// 4. Строки из бинарных операций с литералами (compile-time)
String s = 10 + " students";  // "10 students" в Pool

// ❌ НЕ в Pool:

// 1. new String("text")
String s = new String("hello");

// 2. Результаты runtime конкатенации
String a = "hello";
String b = " world";
String c = a + b;  // НЕ в Pool (даже если оба литеральные)

// 3. Результаты методов (если не intern())
String s = "hello".toUpperCase();  // НЕ в Pool (даёт новый объект)

Performance: Pool vs No Pool

public class StringPoolPerformance {
    public static void main(String[] args) {
        // Сценарий: используем одно и то же значение много раз
        
        // С Pool (литеральные строки)
        long start = System.nanoTime();
        for (int i = 0; i < 1_000_000; i++) {
            String s = "hello";  // переиспользует один объект
        }
        long literalTime = System.nanoTime() - start;
        
        // Без Pool (new String)
        start = System.nanoTime();
        for (int i = 0; i < 1_000_000; i++) {
            String s = new String("hello");  // создает новый объект каждый раз
        }
        long newStringTime = System.nanoTime() - start;
        
        System.out.println("Literal time: " + literalTime);
        System.out.println("new String time: " + newStringTime);
        System.out.println("Разница: " + (newStringTime / literalTime) + "x медленнее");
        // Результат: new String в 10-100x медленнее!
    }
}

Memory impact

// Пример: большое приложение с повторяющимися строками

public class MemoryExample {
    List<User> users = new ArrayList<>();  // 1 миллион пользователей
    
    // ❌ Плохо: каждый User хранит свою копию страны
    class User {
        String name;
        String country;  // "USA", "UK", "China" — повторяется!
    }
    
    // Представим: 1 млн пользователей
    // 50% из США — это 500,000 копий строки "USA"
    // При 3 байта на символ: 500,000 * 3 bytes = 1.5 MB впустую!
    
    // ✅ Хорошо: использовать Pool
    public String getCountry(String rawCountry) {
        // intern() гарантирует, что одна строка в памяти
        return rawCountry.intern();
    }
}

Осторожность с intern()

// ⚠️ intern() может быть опасен!

public class InternCaution {
    public static void main(String[] args) {
        // Проблема: если вызвать intern() на много уникальных строк,
        // Pool переполнится и будет memory leak
        
        // ❌ Плохо:
        for (int i = 0; i < 1_000_000; i++) {
            String uniqueString = "unique_" + i;
            uniqueString.intern();  // добавляем каждую уникальную строку в Pool
        }
        // Pool переполнится, это может привести к OutOfMemoryError!
        
        // ✅ Хорошо:
        // Используй intern() только для ИЗВЕСТНОГО набора строк
        Set<String> KNOWN_COUNTRIES = Set.of("USA", "UK", "France", ...);
        for (String country : KNOWN_COUNTRIES) {
            country.intern();  // безопасно, фиксированное количество
        }
    }
}

String Pool в разных версиях Java

Java 6 и раньше:
- Pool в PermGen (отдельная область памяти)
- Размер фиксирован и мал (~64 KB)
- При переполнении: OutOfMemoryError

Java 7+:
- Pool на Heap
- Динамический размер (растет вместе с Heap)
- При GC может быть очищен
- Гораздо более flexibilе

Java 8+ / modern versions:
- String deduplication в G1GC
- Автоматическое объединение похожих строк
- Меньше нужно думать о Pool

Практические рекомендации

1. Используй литеральные строки где возможно

// ✅ Хорошо — автоматически в Pool
if (country.equals("USA")) { }

// ❌ Плохо — создаешь новый объект
if (country.equals(new String("USA"))) { }

2. == vs equals()

// == проверяет, указывают ли на один объект
String s1 = "hello";
String s2 = "hello";
s1 == s2  // true (оба в Pool)

String s3 = new String("hello");
s1 == s3  // false (разные объекты)

// ВСЕГДА используй equals() для сравнения значений!
// == может дать неожиданный результат

3. intern() только для контролируемых наборов

// ✅ Безопасно
Set<String> KNOWN_STATUSES = Set.of("PENDING", "COMPLETED", "FAILED");
for (String status : KNOWN_STATUSES) {
    status.intern();
}

// ❌ Опасно
for (String randomData : externaldataFromUser) {
    randomData.intern();  // может быть миллионы уникальных строк
}

Вывод

String Pool — это важный механизм оптимизации в Java:

  1. Литеральные строки автоматически в Pool и переиспользуются
  2. new String() создает отдельный объект вне Pool
  3. intern() добавляет строку в Pool вручную (осторожно!)
  4. == для строк проверяет identity, не используй для сравнения значений
  5. Memory impact значительный для повторяющихся данных

На собеседовании важно показать, что понимаешь:

  • Разницу между литеральными строками и new String()
  • Где находится Pool и как работает
  • Когда есть смысл использовать intern()
  • Важность equals() вместо == для строк
Что такое Pool строк? | PrepBro