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

Где могут храниться строки в памяти в Java?

2.0 Middle🔥 201 комментариев
#JVM и управление памятью#Основы Java

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

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

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

# Где хранятся строки в памяти в Java

Строки в Java могут храниться в двух основных местах: String Pool (String Interning Pool) в памяти, которая изолирована от обычного Heap, и в самом Heap.

String Pool — специальная область памяти

String Pool — это специальная память для хранения интернированных строк. Это оптимизация Java для экономии памяти, так как одинаковые строки хранятся один раз.

Строка в String Pool (до Java 7)

// До Java 7: String Pool находился в PermGen
String s1 = "Hello";
String s2 = "Hello";

system.out.println(s1 == s2); // true (одна и та же строка в PermGen)

Строка в String Pool (Java 7+)

// Java 7+: String Pool переместился в Heap
String s1 = "Hello";   // Создает в String Pool (часть Heap)
String s2 = "Hello";   // Находит в String Pool, не создает новую
String s3 = new String("Hello"); // Создает в Heap (вне Pool)

system.out.println(s1 == s2); // true (обе ссылаются на одну строку)
system.out.println(s1 == s3); // false (разные объекты)
system.out.println(s1.equals(s3)); // true (одинаковое содержание)

Визуализация памяти для строк

Java 7+ Памяти:

Heap:
┌─────────────────────────────────────────────────┐
│                  Heap (GC)                      │
│  ┌────────────────────────────────────────┐    │
│  │      String Pool (String interning)   │    │
│  │  ┌──────────────────┐                 │    │
│  │  │ "Hello" -> obj1  │ (одна копия)   │    │
│  │  └──────────────────┘                 │    │
│  └────────────────────────────────────────┘    │
│                                                 │
│  Обычные объекты:                              │
│  ┌──────────────────┐                          │
│  │ "Hello" -> obj2  │ (другой объект)         │
│  └──────────────────┘                          │
│  ┌──────────────────┐                          │
│  │ "World" -> obj3  │                          │
│  └──────────────────┘                          │
└─────────────────────────────────────────────────┘

Stack:
┌─────────────────┐
│ s1 -> obj1      │ (ссылка на String Pool)
│ s2 -> obj1      │ (ссылка на String Pool)
│ s3 -> obj2      │ (ссылка на Heap)
└─────────────────┘

Способы создания строк

1. Литерал строки → String Pool

String s = "Hello";  // Автоматически в String Pool

// При компиляции Java видит:
// - Является ли "Hello" уже в Pool?
// - Если да → используй существующую
// - Если нет → добавь в Pool

2. Оператор new → обычный Heap

String s = new String("Hello");

// Процесс:
// 1. Проверяет String Pool на "Hello"
//    - Если есть, запоминает ссылку
//    - Если нет, добавляет в Pool
// 2. Создает НОВЫЙ объект в Heap (вне Pool)
// 3. s ссылается на объект в Heap, а не Pool

String s1 = "Hello";
String s2 = new String("Hello");
System.out.println(s1 == s2); // false (разные объекты)
System.out.println(s1.equals(s2)); // true (одинаковое содержание)

3. Конкатенация строк → Heap

String s1 = "Hello";
String s2 = "Hello";
String s3 = s1 + s2;  // Результат в Heap (вне Pool)

System.out.println(s3 == "HelloHello"); // false

// Почему? Компилятор оптимизирует только литералы:
String optimized = "Hello" + "Hello"; // Компилятор делает → "HelloHello"
system.out.println(optimized == "HelloHello"); // true

4. intern() → String Pool

String s1 = new String("Hello");  // Heap
String s2 = s1.intern();           // Добавляет в Pool и возвращает ссылку

System.out.println(s1 == s2); // false (s1 → Heap, s2 → Pool)
System.out.println(s2 == "Hello"); // true (Pool)

// Использование intern для кэширования:
String city = getUserCity().intern(); // Кэшируем часто используемые значения

Примеры в разных сценариях

Сценарий 1: Читаются строки из файла

BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
String line;

while ((line = reader.readLine()) != null) {
    // line находится в Heap (вне Pool)
    // Каждая строка из файла создается как новый объект
    
    // Если нужна экономия памяти для повторяющихся значений:
    line = line.intern(); // Добавит в Pool
}

Сценарий 2: Разные операции со строками

public void stringOperations() {
    // 1. Литерал → Pool
    String s1 = "java";
    
    // 2. new → Heap
    String s2 = new String("java");
    
    // 3. substring → Heap (не в Pool по умолчанию)
    String s3 = s1.substring(0, 2); // "ja" → Heap
    
    // 4. toLowerCase → Heap
    String s4 = "JAVA".toLowerCase(); // "java" → Heap
    
    // 5. Конкатенация → Heap
    String s5 = "java" + "script"; // → Heap
    
    // 6. format → Heap
    String s6 = String.format("Hello %s", "world"); // → Heap
    
    // 7. join → Heap
    String s7 = String.join("-", "a", "b", "c"); // → Heap
}

Сценарий 3: Сравнение строк

String a = "hello";
String b = "hello";
String c = new String("hello");
String d = c.intern();

// Где они находятся?
// a → Pool
// b → Pool (та же ссылка, что и a)
// c → Heap
// d → Pool

System.out.println(a == b);     // true (Pool)
System.out.println(a == c);     // false (Pool vs Heap)
System.out.println(a == d);     // true (Pool)
System.out.println(a.equals(c)); // true (содержание)

Проблемы с String Pool

1. OutOfMemoryError

// Если добавлять слишком много строк в Pool через intern()
for (int i = 0; i < 1_000_000; i++) {
    String s = new String(i + "").intern();
    // ОПасно: Pool переполнится
}

// Результат: OutOfMemoryError: Java heap space

2. Неопредсказуемое поведение

String s1 = "test" + "_" + System.currentTimeMillis();
// Результат всегда в Heap, никогда в Pool

String s2 = "test" + "_" + "123"; // Может оптимизироваться в "test_123" → Pool

Контроль String Pool

StringTableSize (по умолчанию 60013)

# Java опция для изменения размера Pool
java -XX:StringTableSize=100000 MyApp

# Слишком большой размер → больше памяти
# Слишком маленький размер → медленнее выполняется

Лучшие практики

// 1. Не используй intern() без необходимости
String s = userInput.intern(); // ❌ Опасно

// 2. Используй intern() только для известных значений
String country = getUserCountry().intern(); // Может быть OK если ограниченное количество

// 3. Сравни строки через equals(), не ==
if (s1.equals(s2)) { } // ✓ Правильно
if (s1 == s2) { }      // ❌ Неправильно (зависит от памяти)

// 4. Используй StringBuilder для конкатенации в цикле
StringBuilder sb = new StringBuilder();
for (String s : strings) {
    sb.append(s); // Эффективнее чем s1 + s2 + s3 + ...
}

// 5. Будь осторожен с кэшированием через intern()
Map<String, String> cache = new HashMap<>();
cache.put(s1.intern(), value); // Может привести к утечке памяти

Вывод

  • Литеральные строки ("Hello") → String Pool (в Heap)
  • new String(...) → обычный Heap
  • Результаты методов (substring, toUpperCase и т.д.) → обычный Heap
  • intern() → добавляет в String Pool
  • == для строк опасно → используй equals()
  • String Pool помогает экономить память для повторяющихся строк
  • Java 7+: String Pool в Heap, управляется GC
Где могут храниться строки в памяти в Java? | PrepBro