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

С чем связано выделение памяти для новой строки

1.0 Junior🔥 181 комментариев
#ORM и Hibernate#Spring Boot и Spring Data#Базы данных и SQL

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

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

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

Выделение памяти для строк в Java: String Pool и Immutability

Выделение памяти для новой строки в Java связано с несколькими концепциями: String Pool (пул строк), immutability (неизменяемость) и heap memory (память в куче). Это важная тема для оптимизации памяти и производительности.

1. String Pool (пул строк) — основная идея

Java оптимизирует память для строк через String Pool — специальное хранилище, где хранятся уникальные строковые значения:

// Пример 1: Создание строки через литерал
String s1 = "Hello";      // Создаёт новую строку в String Pool
String s2 = "Hello";      // Реиспользует ту же строку из Pool!

System.out.println(s1 == s2);        // true (один и тот же объект в памяти)
System.out.println(s1.equals(s2));   // true (значения одинаковые)

// Пример 2: Создание через new
String s3 = new String("Hello");     // Создаёт новый объект в Heap
String s4 = new String("Hello");     // Создаёт ДРУГОЙ объект в Heap

System.out.println(s3 == s4);        // false (разные объекты)
System.out.println(s3.equals(s4));   // true (значения одинаковые)

Почему это важно?

  • String Pool экономит память
  • Сравнение == быстро (просто сравнивает адреса)
  • Но нужно быть осторожным с == vs equals()

2. Неизменяемость (Immutability) строк

Все строки в Java immutable — неизменяемые. Когда вы меняете строку, создаётся новая строка в памяти:

String original = "Hello";  // В String Pool
String modified = original.toUpperCase();  // Создаёт НОВУЮ строку "HELLO"

System.out.println(original);  // Всё ещё "Hello" (не изменилась)
System.out.println(modified);  // "HELLO" (новая строка)
System.out.println(original == modified);  // false

Почему строки immutable?

// Приватное поле char[] value — не может быть изменено
public final class String implements Serializable, Comparable<String> {
    private final char[] value;  // final — нельзя переприсвоить
    private int hash;            // Кэш хэша
    
    public String toUpperCase() {
        // Не меняет текущую строку, а создаёт новую
        char[] chars = this.value.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i] = Character.toUpperCase(chars[i]);
        }
        return new String(chars);  // НОВАЯ строка
    }
}

Плюсы immutability:

  • Thread-safe — несколько потоков могут безопасно использовать одну строку
  • String Pool возможен — если строку никогда не менять, можно делиться ею
  • Caching хэша — поскольку строка не меняется, хэш кэшируется
  • HashMap ключи — строки безопасны как ключи в HashMap
// HashMap без immutability был бы ненадёжен
Map<String, Integer> map = new HashMap<>();
String key = new String("name");
map.put(key, 25);

// Если бы строку можно было изменить:
// key.modify("othername");  // ← это нарушило бы HashMap
// map.get("name");          // ← не найдёт значение!

3. Процесс выделения памяти

Когда вы создаёте новую строку, происходит следующее:

// Шаг 1: String литерал
String s = "Hello World";

// JVM проверяет String Pool:
// 1. Есть ли уже строка "Hello World" в пуле?
//    - ДА → возвращает ссылку на существующий объект
//    - НЕТ → создаёт новый объект в пуле, выделяет память

// Шаг 2: new String()
String s2 = new String("Hello World");

// JVM:\n// 1. Выделяет память в Heap (НЕ в String Pool)
// 2. Копирует данные из литерала в новый объект
// 3. Возвращает ссылку на новый объект в Heap

4. Проблема: String concatenation

Одна из главных причин неправильного выделения памяти:

// ❌ Неправильно — создаёт много новых объектов
String result = "";
for (int i = 0; i < 1000; i++) {
    result = result + i;  // Каждый раз создаёт новую строку!
}
// Это создаст 1000 новых объектов в памяти

// Визуально:
// Итерация 1: "" + 0 = new String("0")
// Итерация 2: "0" + 1 = new String("01")
// Итерация 3: "01" + 2 = new String("012")
// ... и так далее

Решение: StringBuilder (мutable строка)

// ✅ Правильно — изменяемая строка в памяти
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);  // Расширяет существующий объект
}
String result = sb.toString();  // Преобразует в String в конце

// StringBuilder выделяет память один раз (с резервом),
// а потом просто добавляет данные

Сравнение памяти:

@Test
public void memoryComparisonTest() {
    long startMemory = Runtime.getRuntime().totalMemory();
    
    // Неправильно: String concatenation
    String result = "";
    for (int i = 0; i < 10000; i++) {
        result = result + i;  // Очень неэффективно
    }
    long stringTime = System.currentTimeMillis();
    
    // Правильно: StringBuilder
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 10000; i++) {
        sb.append(i);
    }
    String result2 = sb.toString();
    long builderTime = System.currentTimeMillis();
    
    System.out.println("String: " + stringTime + " ms");
    System.out.println("StringBuilder: " + builderTime + " ms");
    // StringBuilder обычно в 100+ раз быстрее!
}

5. String intern() — добавить в String Pool

Если вы создали строку через new, можно добавить её в пул:

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

System.out.println(s2 == s3);      // true (одна и та же объект из пула)
System.out.println(s1 == s2);      // false (s1 всё ещё в Heap)

Когда использовать intern():

  • Редко — обычно это преждевременная оптимизация
  • Если у вас много дублирующихся строк из внешних источников
  • Осторожно — String Pool имеет ограниченный размер

6. Оптимизация памяти при работе со строками

Правило 1: используй StringBuilder для конкатенации

// ❌ Плохо
String query = "SELECT * FROM users WHERE id = " + userId + " AND status = " + status;

// ✅ Хорошо
String query = new StringBuilder()
    .append("SELECT * FROM users WHERE id = ")
    .append(userId)
    .append(" AND status = ")
    .append(status)
    .toString();

// ✅ Или используй String.format
String query = String.format("SELECT * FROM users WHERE id = %d AND status = %s", userId, status);

Правило 2: сравнивай строки правильно

// ❌ Неправильно
if (str1 == str2) { }  // Сравнивает адреса в памяти

// ✅ Правильно
if (str1.equals(str2)) { }  // Сравнивает содержимое

// ✅ Case-insensitive
if (str1.equalsIgnoreCase(str2)) { }

Правило 3: кэш строки, если используешь часто

// Если эта строка используется 1000 раз
private static final String CONSTANT_STRING = "Authorization";

public void process(Request request) {
    String header = request.getHeader(CONSTANT_STRING);
    // Вместо: request.getHeader("Authorization")  разные объекты
}

7. Memory Leak: забытые строки

Даже с Garbage Collector могут быть проблемы:

// ❌ Потенциальный memory leak
public class Cache {
    private static final List<String> cache = new ArrayList<>();
    
    public void addToCache(String key, String value) {
        String cacheEntry = key + ":" + value;  // Новая строка
        cache.add(cacheEntry);  // Добавляем в статический список
        // Список никогда не очищается → утечка памяти
    }
}

// ✅ Правильно — с максимальным размером
public class Cache {
    private static final LinkedHashMap<String, String> cache = 
        new LinkedHashMap<String, String>(16, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > 1000;  // Максимум 1000 элементов
            }
        };
}

Вывод

Выделение памяти для строк связано с:

  1. String Pool — JVM переиспользует строки для экономии памяти
  2. Immutability — строки не меняются, каждое изменение = новая строка
  3. Heap vs Pool — литералы в пуле, new String() в куче
  4. Оптимизация — используй StringBuilder вместо + для конкатенации
  5. Thread-safety — именно благодаря immutability строки потокобезопасны

Это базовое понимание критично для написания эффективного Java кода и диагностики проблем с памятью.

С чем связано выделение памяти для новой строки | PrepBro