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

Как String хранится в памяти

2.0 Middle🔥 151 комментариев
#JVM и управление памятью

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

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

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

# Как String хранится в памяти

Особенность String: Immutable (неизменяемость)

String в Java — это **immutable** (неизменяемый) объект. Это одна из самых важных особенностей, которая влияет на то, как String хранится в памяти.

Внутренняя структура String

Java 8 и ранее

public final class String {
    private final char[] value;      // Массив символов
    private final int offset;        // Смещение в массиве
    private final int count;         // Количество символов
    private int hash;                // Кэшированный хэш
    
    public String(String original) {
        this.value = original.value;
        this.offset = original.offset;
        this.count = original.count;
        this.hash = original.hash;
    }
}

Java 9 и позже (Compact Strings)

С Java 9 введена оптимизация Compact Strings:

public final class String {
    // Вместо char[] используется byte[]
    private final byte[] value;      // Один или два байта на символ
    private final byte coder;        // LATIN1 (1 байт) или UTF16 (2 байта)
    private int hash;                // Кэшированный хэш
}

Где хранится String в памяти

String Pool (Пул строк)

Все строк-литералы хранятся в специальной памяти — String Pool (в Java 8 в пермgen, с Java 8+ в heap):

public class StringMemoryExample {
    public static void main(String[] args) {
        // Пример 1: String literal → String Pool
        String s1 = "Hello";      // Создаётся в String Pool
        String s2 = "Hello";      // Ссылается на ту же строку в Pool
        
        System.out.println(s1 == s2);  // true (один объект)
        System.out.println(s1.equals(s2));  // true
        
        // Пример 2: String с new → Heap
        String s3 = new String("Hello");  // Новый объект на heap
        String s4 = new String("Hello");  // Ещё один новый объект
        
        System.out.println(s3 == s4);     // false (разные объекты)
        System.out.println(s3.equals(s4)); // true (одинаковое содержимое)
        
        // Пример 3: intern() — добавить в Pool
        String s5 = new String("Hello").intern();  // Добавляется в Pool
        System.out.println(s1 == s5);  // true (теперь один объект)
    }
}

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

┌─────────────────────────────────────┐
│   STRING POOL (в Heap, Java 8+)     │
│                                     │
│  ┌──────────────────────────────┐  │
│  │ "Hello" (byte[] или char[])  │◄─┼─── s1, s2, s5 указывают сюда
│  └──────────────────────────────┘  │
│                                     │
│  ┌──────────────────────────────┐  │
│  │ "World" (byte[] или char[])  │  │
│  └──────────────────────────────┘  │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│   HEAP (остальные объекты)          │
│                                     │
│  s3 → ┌──────────────────────────┐ │
│       │ "Hello" (отдельный)      │ │
│       └──────────────────────────┘ │
│                                     │
│  s4 → ┌──────────────────────────┐ │
│       │ "Hello" (отдельный)      │ │
│       └──────────────────────────┘ │
└─────────────────────────────────────┘

Механизм String Interning

Автоматический Interning (String Literals)

String s1 = "Java";     // Автоматически добавляется в Pool
String s2 = "Java";     // Используется из Pool
String s3 = "Ja" + "va"; // Компилятор оптимизирует → компилирует как "Java"

String s4 = "Ja";
String s5 = s4 + "va";  // Runtime конкатенация, НЕ в Pool

Явный Interning

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

String s3 = "Hello";
System.out.println(s2 == s3);  // true (оба из Pool)

Immutability и его следствия

Почему String immutable

public class StringImmutability {
    // 1. Security: пароли, ключи хранятся в String
    String password = "secret123";
    // Нельзя изменить → безопаснее
    
    // 2. String Pool работает
    // Если бы String было изменяемым, Pool развалилась бы
    String s1 = "Hello";
    String s2 = "Hello";  // указывает на то же место
    // s1.changeCharAt(0, 'J'); // Если бы было возможно, s2 тоже изменится!
    
    // 3. Thread safety
    // Immutable объекты безопасны для многопоточности
    // Не нужна синхронизация
}

Операции с String (создают новые объекты)

String s = "Hello";

// Каждая операция создаёт НОВЫЙ String
String s1 = s.toUpperCase();   // "HELLO" → новый объект
String s2 = s.substring(1);    // "ello" → новый объект
String s3 = s.replace('l', 'L'); // "HeLLo" → новый объект
String s4 = s.concat(" World");  // "Hello World" → новый объект

// Исходный s остаётся "Hello"

Оптимизация String (StringBuilder/StringBuffer)

Проблема: неэффективная конкатенация

String result = "";
for (int i = 0; i < 1000; i++) {
    result += "item" + i;  // ❌ Создаёт 1000+ новых String объектов!
}
// Это очень неэффективно для памяти и производительности

Решение: StringBuilder

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("item").append(i);  // ✅ Эффективно
}
String result = sb.toString();  // Один новый String

// Различие:
// - String (immutable): безопасен, медленен при конкатенации
// - StringBuilder (mutable): быстрый, НЕ потокобезопасен
// - StringBuffer (mutable): быстрый, потокобезопасен (синхронизирован)

Compact Strings в Java 9+

public class CompactStrings {
    // До Java 9: каждый символ = 2 байта (char)
    // Строка "Hello" = 10 байт (5 символов × 2 байта)
    
    // С Java 9: если все символы < 256 (LATIN1)
    // Строка "Hello" = 5 байт (1 байт на символ)
    // Строка "Привет" = 12 байт (UTF16, 2 байта на символ)
    
    // Автоматическое переключение между LATIN1 и UTF16
    byte coder;  // 0 = LATIN1, 1 = UTF16
}

Практические советы

public class StringBestPractices {
    // 1. Используй StringBuilder для конкатенации в цикле
    // ❌ String result = ""; for (i) result += ...;
    // ✅ StringBuilder sb = new StringBuilder(); for (i) sb.append(...);
    
    // 2. Используй == для сравнения литералов в switch
    switch (status) {
        case "active":
            break;  // ✅ Быстро (String Pool)
    }
    
    // 3. Будь осторожен с intern() в продакшене
    // Может привести к утечкам памяти, если добавлять много строк
    
    // 4. Помни о String Pool при работе с большим числом уникальных строк
    // У String Pool конечный размер и оно может переполниться
}

Вывод

String в Java хранится:
  • String literals → в String Pool (heap, Java 8+)
  • String объекты с new → в обычной heap памяти
  • Внутренне: массив char[] (Java 8) или byte[] (Java 9+)
  • Immutable: не может быть изменён после создания
  • Оптимизирован: с Java 9 используется Compact Strings для экономии памяти