Живёт ли интернированная строка до завершения JVM?
Нет. Интернированная строка живёт, пока на неё существует сильная достижимая ссылка, например из работающего кода, статического поля или загруженного класса. Если таких ссылок нет, String pool не обязан удерживать объект до завершения JVM: сборщик мусора может удалить его.
Пул строк появился как механизм каноникализации: одинаковые строковые значения могут совместно использовать один объект. Это уменьшает дублирование строк и позволяет сравнивать канонические экземпляры по ссылке, хотя для обычной проверки содержимого по-прежнему используют equals.
Такой подход особенно полезен для строковых литералов и ограниченного набора повторяющихся значений. Он не предназначен как универсальный кэш всех динамически создаваемых строк.
Ошибочно считать, что вызов intern() навсегда помещает строку в память. При большом количестве уникальных динамических значений это может привести к росту числа записей в таблице интернированных строк и дополнительным затратам на поиск и обработку этих объектов.
Обратная ошибка тоже возможна: приложение может сохранить только результат сравнения или временно получить каноническую строку, а затем потерять сильную ссылку. Впоследствии объект может быть собран, поэтому нельзя использовать интернирование как гарантию бессрочного хранения значения.
В HotSpot интернированные строки учитываются в специальной таблице строк. Эта таблица не превращает каждую строку в бессрочно живой объект: при сборке мусора JVM может очистить записи, если соответствующая строка недостижима из обычных сильных ссылок.
Строковый литерал обычно удерживается, пока жив загруженный класс, который на него ссылается через свой пул констант. После выгрузки класса такая ссылка может исчезнуть. Динамически интернированная строка также может быть удалена, если её больше никто сильно не удерживает.
Важно различать два факта:
intern() возвращает канонический объект, но сам вызов не гарантирует его бессрочное хранение;Интернирование также не делает создание большого множества уникальных строк безопасным. Оно может сократить дублирование при повторяющихся значениях, но для неограниченного потока идентификаторов чаще подходят обычный ограниченный кэш, словарь каноникализации или отказ от каноникализации.
Сервис интернировал внешние идентификаторы клиентов, чтобы уменьшить число одинаковых строк. В штатном трафике это помогало, но при загрузке большого набора уникальных идентификаторов увеличивались задержки сборки мусора и объём памяти, связанный с таблицей строк.
Рассматривались два варианта. Полный отказ от интернирования был простым и предсказуемым, но сохранял дубликаты часто повторяющихся значений. Неограниченное интернирование уменьшало дубликаты, однако не ограничивало число уникальных ключей и создавало риск роста служебных структур.
Выбрали ограниченный кэш канонических значений с политикой вытеснения. Он обеспечил повторное использование наиболее частых строк, ограничил верхнюю границу памяти и сделал поведение предсказуемее. Интернирование оставили только для небольших заранее ограниченных наборов значений.
Вопрос: Можно ли полагаться на == после интернирования строк?
Ответ: Только если обе ссылки действительно указывают на канонические экземпляры, например после интернирования обеих строк или при использовании строковых литералов. Само происхождение строки этого не гарантирует: строка, полученная из внешнего источника или конкатенации, может быть отдельным объектом. Для проверки равенства значений применяют equals, а == сравнивает ссылки.
Вопрос: Удаляется ли строка из пула сразу после потери последней сильной ссылки?
Ответ: Нет, такой момент не гарантируется. Сборщик мусора запускается по своим условиям, а очистка таблицы строк происходит в рамках работы JVM и конкретного цикла GC. Поэтому отсутствие сильных ссылок означает возможность удаления, но не немедленное удаление.
Вопрос: Почему интернирование не всегда уменьшает расход памяти?
Ответ: Для повторяющихся значений один канонический объект действительно может заменить множество дубликатов. Но уникальные значения всё равно требуют отдельных объектов, а сама таблица интернированных строк и обработка её записей добавляют накладные расходы. Кроме того, временные строки могут сначала создаваться, а затем интернироваться, поэтому пиковое потребление памяти и стоимость CPU не исчезают автоматически.