Как JVM работает со строками, часть 2



Ссылка на часть 1.

Сегодня рассмотрим ещё 2 простых, но эффективных приёма JVM по оптимизации строк.



Оба связаны со внутренним устройством String:

byte[] value — текст

byte coder — тип кодировки

int hash



1️⃣ String Deduplication (Java 8)

При использовании оператора new в памяти создаётся новый объект:

String str = new String("12");



Отдельный JVM поток ищет в памяти экземпляры String и записывает их текст в хэш-таблицу:

hash → byte[]



Если поток находит дубликат, то заменяет ссылку в повторяющейся строке на ссылку из хэш-таблицы:

str.value = hashtable.value



На текст дубликата больше нет ссылок, поэтому он удалится сборщиком мусора. Так можно сэкономить 5-30% памяти. Опция String Deduplication работает только со сборщиком G1, и по умолчанию отключена.



Почему заменяется только текст, а не вся строка?

Ссылки на строчку могут быть в десятках мест. Текст — внутреннее поле, его заменить гораздо проще



Почему совместим только с G1?

Нашла только один комментарий по теме: G1 – единственный сборщик мусора, в котором есть pinned regions. Это области, в которых не происходит дефрагментация. Но почему нельзя разместить хэш-таблицу в другом месте — непонятно😕



Почему опция по умолчанию не работает?

Deduplication хорошо экономит память, когда в программе через оператор new создаётся много похожих строк-долгожителей. Тогда экономится больше памяти, чем затраты на копирование в хэш-таблицу. По статистике OpenJDK таких случаев мало, поэтому StringDeduplication по умолчанию отключен.



Для активации добавьте при запуске VM флажок:

-XX:+UseStringDeduplication



Чтобы посмотреть, сколько памяти сэкономил Deduplication, можно вывести статистику Xlog:

-Xlog:stringdedup*=debug



2️⃣ Оптимизация хэш-кода (Java 13)

Хэшкод строки играет важную роль в хэш-таблицах String pool, Deduplication и других процессах JVM.



Внутри класса String хэш хранится как примитив:

int hash



Вычисляется и сохраняется при первом вызове hashcode():

if (hash == 0) {

hash = ...

}



⛔️Минус такого решения:

Если хэш равен 0, то он пересчитывается каждый раз. Шанс этого очень низкий, но в Java 13 в String добавился флаг для нулевого хэша:

boolean hashIsZero



Проверка того, что хэш-код ещё не посчитан, стала такой:

if (h == 0 && !hashIsZero)



Теперь хэш-код всегда считается один раз.



Итого: оптимизацией строк идёт по всем фронтам:

1️⃣ Структура данных (Java 9: Compact Strings)

2️⃣ Кэширование (String pool)

3️⃣ Оптимизация частных случаев (Java 8: String Deduplication)

4️⃣ Микрооптимизации (Java 13: флажок hashIsZero)