Как JVM работает со строками, часть 2
Ссылка на часть 1.
Сегодня рассмотрим ещё 2 простых, но эффективных приёма JVM по оптимизации строк.
Оба связаны со внутренним устройством String:
При использовании оператора new в памяти создаётся новый объект:
❓Почему заменяется только текст, а не вся строка?
Ссылки на строчку могут быть в десятках мест. Текст — внутреннее поле, его заменить гораздо проще
❓Почему совместим только с G1?
Нашла только один комментарий по теме: G1 – единственный сборщик мусора, в котором есть pinned regions. Это области, в которых не происходит дефрагментация. Но почему нельзя разместить хэш-таблицу в другом месте — непонятно😕
❓Почему опция по умолчанию не работает?
Deduplication хорошо экономит память, когда в программе через оператор new создаётся много похожих строк-долгожителей. Тогда экономится больше памяти, чем затраты на копирование в хэш-таблицу. По статистике OpenJDK таких случаев мало, поэтому StringDeduplication по умолчанию отключен.
Для активации добавьте при запуске VM флажок:
Хэшкод строки играет важную роль в хэш-таблицах String pool, Deduplication и других процессах JVM.
Внутри класса String хэш хранится как примитив:
Если хэш равен 0, то он пересчитывается каждый раз. Шанс этого очень низкий, но в Java 13 в String добавился флаг для нулевого хэша:
Итого: оптимизацией строк идёт по всем фронтам:
1️⃣ Структура данных (Java 9: Compact Strings)
2️⃣ Кэширование (String pool)
3️⃣ Оптимизация частных случаев (Java 8: String Deduplication)
4️⃣ Микрооптимизации (Java 13: флажок hashIsZero)
Ссылка на часть 1.
Сегодня рассмотрим ещё 2 простых, но эффективных приёма JVM по оптимизации строк.
Оба связаны со внутренним устройством String:
byte[] value
— текстbyte coder
— тип кодировкиint hash1️⃣ 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*=debug2️⃣ Оптимизация хэш-кода (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)