JEP 250: Store Interned Strings in CDS Archives
Хранение интернированных строк в архивах CDS
| Автор | Jiangli Zhou |
| Ответственный | Ioi Lam |
| Тип | Feature |
| Область | JDK |
| Статус | Closed / Delivered |
| Выпуск | 9 |
| Компонент | hotspot / runtime |
| Обсуждение | hotspot dash dev at openjdk dot java dot net |
| Связан с | JEP 254: Compact Strings |
| Рецензенты | Karen Kinnear, Mikael Vidstedt |
| Одобрен | Mikael Vidstedt |
| Создан | 2014/09/24 23:49 |
| Обновлён | 2022/10/03 17:25 |
| Задача | 8059092 |
Аннотация
Хранить интернированные строки в архивах class-data sharing (CDS, совместное использование данных классов).
Цели
-
Сократить потребление памяти за счёт совместного использования объектов
Stringи лежащих в их основе объектов-массивовcharразными процессами JVM. -
Поддерживать разделяемые строки только для G1 GC. Разделяемым строкам нужен закреплённый (pinned) регион, а G1 — единственный сборщик мусора в HotSpot, который поддерживает закрепление регионов.
-
Поддерживать только 64-битные платформы со сжатыми указателями на объекты и классы.
-
Нет существенного ухудшения (< 2–3 %) времени запуска, времени поиска строк, времени паузы GC или производительности во время выполнения на обычных бенчмарках.
Что не является целью
-
Сокращение времени запуска не является целью.
-
Другие сборщики мусора (кроме G1) поддерживаться не будут.
-
32-битные платформы поддерживаться не будут.
Мотивация
Сейчас, когда CDS сохраняет классы в архив, элементы CONSTANT_String в пулах констант представлены строками UTF-8. При загрузке класса строки UTF-8 по мере необходимости преобразуются в объекты java.lang.String. Из-за этого память может расходоваться впустую, поскольку каждый символ каждой интернированной строки занимает три байта или больше (два байта в String, 1–3 байта в UTF-8).
Кроме того, поскольку строки создаются динамически, их нелегко совместно использовать в разных процессах JVM.
Описание
Во время создания дампа при инициализации кучи в куче Java выделяется специальное пространство для строк. При записи таблицы интернированных строк и объектов String указатели на интернированные объекты String и лежащие в их основе объекты-массивы char изменяются так, как если бы эти объекты находились в этом специальном пространстве.
Во время создания дампа таблица строк сжимается и затем сохраняется в архиве. Для сжатия таблицы строк применяется тот же способ, что и для разделяемой таблицы символов (см. JDK-8059510). Для доступа к разделяемым объектам String из сжатой таблицы строк используется обычное кодирование и декодирование узких oop (narrow oop).
На 64-битных платформах со сжатыми указателями oop узкие oop кодируются смещениями (с масштабированием или без него) относительно базы узких oop. Сейчас существует четыре режима кодирования: 32-битный без масштабирования, с нулевой базой, на основе непересекающейся кучи и на основе кучи. Подходящий режим кодирования выбирается в зависимости от размера кучи и минимальной базы кучи. Режим кодирования узких oop (включая сдвиг кодирования) должен быть одинаковым и во время создания дампа, и во время выполнения, чтобы указатели oop внутри пространства разделяемых строк оставались действительными во время выполнения. Пространство разделяемых строк можно считать перемещаемым во время выполнения, с ограничениями. Его не обязательно отображать по тому же адресу, что и во время создания дампа, но оно должно находиться на одном и том же смещении от базы узких oop во время создания дампа и во время выполнения. Размер кучи не обязан совпадать во время создания дампа и во время выполнения, если используется один и тот же режим кодирования. Смещение пространства строк и режим кодирования oop (и сдвиг) должны сохраняться в архиве для проверки во время выполнения. Если режим кодирования изменится, кодирование указателя oop на массив char из каждой разделяемой String станет недействительным. В таких случаях данные разделяемых строк игнорируются, а остальные разделяемые данные VM по-прежнему может использовать. VM выведет предупреждение о том, что разделяемые строки не используются из-за несовместимой конфигурации GC.
Во время выполнения пространство строк отображается как часть кучи Java на том же смещении от базы кодирования oop, что и во время создания дампа. Отображение начинается с наименьшего выровненного по странице адреса пространства строк, сохранённого в архиве. Отображённое пространство строк содержит разделяемые объекты String и объекты-массивы char. Все регионы G1, которые пересекаются с этим отображённым пространством, будут помечены как закреплённые; эти регионы G1 недоступны для выделения памяти во время выполнения. В регионе, который пересекается с пространством частично, может оставаться неиспользуемое место, но такой регион должен быть не больше чем один, в конце отображения. Исправлять указатели oop внутри пространства строк не требуется, поскольку используется то же кодирование узких oop. Пространство разделяемых строк доступно для записи, но GC не должен записывать в oop в этом пространстве, чтобы сохранить возможность совместного использования разными процессами. Приложение, которое пытается захватить блокировку одной из этих разделяемых строк и тем самым записывает в разделяемое пространство, получит частную копию страницы и поэтому потеряет преимущество совместного использования этой страницы. Такие случаи редки.
Во время выполнения таблица разделяемых строк отделена от обычной таблицы строк. При поиске интернированных строк просматриваются обе таблицы. Таблица разделяемых строк во время выполнения доступна только для чтения; добавлять в неё записи или удалять их из неё нельзя.
Таблица дедупликации строк G1 — это отдельная хэш-таблица, содержащая массивы char для дедупликации во время выполнения. Когда строка интернируется и добавляется в StringTable, она дедуплицируется, и лежащий в её основе массив char добавляется в таблицу дедупликации, если его там ещё нет. Таблица дедупликации в архив не сохраняется. Таблица дедупликации заполняется при запуске VM с использованием данных разделяемых строк. В качестве оптимизации эта работа выполняется в G1StringDedupThread (в G1StringDedupThread::run(), после initialize_in_thread()), чтобы сократить время запуска. Хэш-значения разделяемых строк вычисляются заранее и сохраняются в строках во время создания дампа, чтобы код дедупликации не записывал хэш-значения во время выполнения.
Тестирование
Тестирование этой возможности охватит следующие области:
-
базовая работа этой возможности;
-
режимы, несовместимые с этой возможностью, например сборщики мусора, отличные от G1, и несжатые указатели на объекты и классы;
-
различие кодирования обычных указателей на объекты (ordinary object pointer) во время создания дампа и во время выполнения;
-
недопустимый формат файла строк;
-
отдельные операции со строками при использовании этой возможности, например интернирование и сравнение строк; и
-
проверка с помощью диагностических режимов GC того, что эта возможность не приводит к повреждению кучи.
Зависимости
Serviceability agent нужно обновить, чтобы добавить поддержку таблицы разделяемых строк (см. JDK-8079830).
С изменением, предложенным в JDK-8054307, лежащий в основе массив char будет заменён массивом byte. Код, который копирует интернированные строки в пространство строк и выполняет дедупликацию, нужно будет изменить с учётом этого, если JDK-8054307 будет интегрирован. Влияние должно быть минимальным.