Слышал ли что-то про метод intern?

Метод intern() в Java — это метод класса String, предназначенный для работы с пулом строк (string pool), то есть специальным хранилищем уникальных строковых литералов в памяти. Он позволяет уменьшать количество объектов в памяти, ускорять сравнение строк и оптимизировать использование памяти в случаях, где часто создаются одинаковые строки.

Общая характеристика метода

Сигнатура:

public String intern()

Описание:
Метод intern() возвращает каноническое представление строки — то есть единственный экземпляр строки, содержащей те же символы, что и исходная, из пула строк. Если такая строка уже есть в пуле, она возвращается. Если её там нет, строка добавляется в пул и возвращается ссылка на неё.

Что такое пул строк (string pool)

Пул строк — это специальная область в памяти, управляемая JVM, где хранятся уникальные строковые литералы. Это означает, что строка "abc", встречающаяся несколько раз в коде как литерал, будет представлена одним и тем же объектом в памяти.

JVM создаёт строки в пуле:

  • автоматически для всех строковых литералов, таких как "hello";

  • вручную — при использовании метода intern().

Поведение метода intern()

Рассмотрим пример:

String a = "hello";
String b = new String("hello");
System.out.println(a == b); // false  разные объекты
System.out.println(a.equals(b)); // true  одинаковое содержимое
String c = b.intern();
System.out.println(a == c); // true  теперь обе строки ссылаются на один объект из пула

Разбор:

  • a — строковый литерал, автоматически попадает в пул;

  • b — новый объект в куче (heap), не из пула;

  • b.intern() возвращает строку "hello" из пула, поэтому c == a.

Когда используется intern()

  1. Оптимизация памяти, особенно когда:

    • создаётся много одинаковых строк (например, при разборе XML, JSON, CSV);

    • строки часто сравниваются с использованием == (что быстрее, чем equals()).

  2. Повышение производительности при:

    • большом количестве операций сравнения строк;

    • использовании хеш-структур, где == предпочтительнее.

  3. Ручное управление пулом строк, если строки создаются динамически (например, на входе из базы данных или файлов).

Недостатки и предостережения

  • intern() не всегда улучшает производительность. При неправильном использовании он может нагрузить сборщик мусора или заполнить Metaspace/PermGen.

  • В Java 6 строки из пула хранились в PermGen (ограниченный по размеру), а в Java 7+ — в Heap, что улучшило ситуацию.

  • Частое использование intern() на уникальных строках может привести к переполнению памяти или уменьшению скорости, так как каждая строка будет сохраняться навсегда (до завершения программы или выгрузки класса).

  • Сравнение == после intern() безопасно только если точно известно, что обе строки прошли через пул.

Пример: поведение без и с intern()

String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2); // false  два разных объекта
System.out.println(s1.intern() == s2.intern()); // true  одна строка в пуле

В этом примере создаются две разные строки, но после применения intern() обе переменные будут ссылаться на одну строку в пуле.

intern() и компиляция литералов

Когда используется строковый литерал (например, "abc"), компилятор автоматически помещает его в пул:

String a = "abc";
String b = "abc";
System.out.println(a == b); // true

Однако при создании строки через new String("abc"), объект попадает в кучу:

String c = new String("abc");
System.out.println(a == c); // false

Только после c.intern() переменная будет ссылаться на строку "abc" из пула.

intern() в Java 6 и Java 7+

В Java 6 пул строк находился в PermGen (Permanent Generation) — специальной области памяти JVM с ограниченным размером. Это ограничение приводило к OutOfMemoryError при активном использовании intern() с большим количеством уникальных строк.

В Java 7 и выше пул строк перенесли в Heap (кучу), и лимит теперь зависит от общего объема памяти JVM, а не от отдельно выделенного PermGen, который был полностью удалён в Java 8 (заменён на Metaspace). Это сделало использование intern() более безопасным.

intern() и String.intern() как оптимизация кэширования

В некоторых случаях метод intern() может использоваться как замена кэшу строк, например:

Map&lt;String, Integer&gt; userCache = new HashMap<>();
for (String line : fileLines) {
String userId = line.split(",")\[0\].intern(); // убирает дубликаты userId
userCache.put(userId, ...);
}

В этом примере intern() экономит память, если userId часто повторяется.

Тест на производительность

В сценариях, где часто создаются дублирующиеся строки:

for (int i = 0; i < 1000000; i++) {
String s = new String("ABCDEF").intern();
}

Здесь создаётся миллион новых строк, но после интернирования в памяти будет существовать только одна строка "ABCDEF" в пуле.

Важные замечания

  • Метод intern() полезен, но должен использоваться обдуманно.

  • Лучше использовать его там, где много дублирующихся строк и часто выполняется сравнение строк.

  • Не рекомендуется применять к строкам, которые гарантированно уникальны (например, случайные UUID), так как это не даст выигрыша в памяти, но увеличит нагрузку на пул.