Подтвердить что ты не робот

Функция дедупликации строк Java 8

Так как String в Java (как и другие языки) потребляет много памяти, потому что каждый символ потребляет два байта, Java 8 представила новую функцию с названием Дедупликация строк, которая использует тот факт, что массивы char являются внутренними для строк и final, поэтому JVM может обходиться с ними.

Я читал этот пример до сих пор, но поскольку я не являюсь профессиональным Java-кодером, мне трудно понять концепцию.

Вот что он говорит,

Были рассмотрены различные стратегии для Stup Duplication, но реализуемый в настоящее время следует следующему подходу: всякий раз, когда сборщик мусора посещает объекты String, он принимает к сведению charмассивы. Он берет свое значение хэш-функции и сохраняет его вместе со слабым ссылка на массив. Как только он найдет другую строку, которая тот же хэш-код сравнивает их char с char. Если они соответствуют ну, одна строка будет изменена и укажет на массив charвторая строка. Первый массив char больше не ссылается больше и может быть собрано мусор.

Весь этот процесс, конечно, приносит некоторые накладные расходы, но контролируется по жестким ограничениям. Например, если строка не найдена дубликатов на некоторое время, когда он больше не будет проверен.

Мой первый вопрос,

По-прежнему нехватка ресурсов по этой теме, так как она недавно добавлена ​​в Java 8 update 20, может ли кто-нибудь здесь поделиться некоторыми практическими примерами того, как это помогает уменьшить память, потребляемую String в Java?

Edit:

В приведенной выше ссылке говорится,

Как только он найдет другую строку, которая имеет тот же хэш-код, она сравнивает их char с помощью char

Мой второй вопрос,

Если хэш-код из двух String одинаковый, то Strings уже совпадают, то зачем сравнивать их char на char после того, как будет найдено, что два String имеют одинаковый хеш-код?

4b9b3361

Ответ 1

Представьте, что у вас есть телефонная книга, в которой есть люди, у которых есть String firstName и String lastName. И бывает, что в вашей телефонной книге 100 000 человек имеют одинаковые firstName = "John".

Поскольку вы получаете данные из базы данных или файла, эти строки не интернированы, поэтому ваша память JVM содержит массив char {'J', 'o', 'h', 'n'} 100 тысяч раз, по одному на строку Джона. Каждый из этих массивов занимает, скажем, 20 байт памяти, поэтому эти 100k Johns занимают 2 МБ памяти.

При дедупликации JVM поймет, что "Джон" дублируется много раз и делает все эти строки Джона указателями на один и тот же базовый массив char, уменьшая использование памяти от 2 МБ до 20 байтов.

Подробное объяснение можно найти в JEP. В частности:

Многие крупномасштабные Java-приложения в настоящее время являются узкими местами в памяти. Измерения показали, что примерно 25% данных Java heap live, установленных в этих типах приложений, потребляются объектами String. Кроме того, примерно половина этих объектов String являются дубликатами, где дубликаты означают string1.equals(string2). Дублирование объектов String в куче, по сути, является пустой тратой памяти.

[...]

Фактическое ожидаемое пособие заканчивается сокращением кучи на 10%. Обратите внимание, что это число является рассчитанным средним значением, основанным на широком спектре приложений. Уменьшение кучи для конкретного приложения может значительно варьироваться как вверх, так и вниз.

Ответ 2

@assylias отвечает basiclly рассказывает вам, как это работает и является очень хорошим ответом. Я протестировал производственное приложение со String Deduplication и получил некоторые результаты. Веб-приложение сильно использует строки, поэтому я думаю, что преимущество довольно ясное.

Чтобы включить дедупликацию строк, вам необходимо добавить эти параметры JVM (вам нужно хотя бы Java 8u20):

-XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics

Последний вариант является необязательным, но, как и название, он показывает статистику Std Deduplication. Вот мои:

[GC concurrent-string-deduplication, 2893.3K->2672.0B(2890.7K), avg 97.3%, 0.0175148 secs]
   [Last Exec: 0.0175148 secs, Idle: 3.2029081 secs, Blocked: 0/0.0000000 secs]
      [Inspected:           96613]
         [Skipped:              0(  0.0%)]
         [Hashed:           96598(100.0%)]
         [Known:                2(  0.0%)]
         [New:              96611(100.0%)   2893.3K]
      [Deduplicated:        96536( 99.9%)   2890.7K( 99.9%)]
         [Young:                0(  0.0%)      0.0B(  0.0%)]
         [Old:              96536(100.0%)   2890.7K(100.0%)]
   [Total Exec: 452/7.6109490 secs, Idle: 452/776.3032184 secs, Blocked: 11/0.0258406 secs]
      [Inspected:        27108398]
         [Skipped:              0(  0.0%)]
         [Hashed:        26828486( 99.0%)]
         [Known:            19025(  0.1%)]
         [New:           27089373( 99.9%)    823.9M]
      [Deduplicated:     26853964( 99.1%)    801.6M( 97.3%)]
         [Young:             4732(  0.0%)    171.3K(  0.0%)]
         [Old:           26849232(100.0%)    801.4M(100.0%)]
   [Table]
      [Memory Usage: 2834.7K]
      [Size: 65536, Min: 1024, Max: 16777216]
      [Entries: 98687, Load: 150.6%, Cached: 415, Added: 252375, Removed: 153688]
      [Resize Count: 6, Shrink Threshold: 43690(66.7%), Grow Threshold: 131072(200.0%)]
      [Rehash Count: 0, Rehash Threshold: 120, Hash Seed: 0x0]
      [Age Threshold: 3]
   [Queue]
      [Dropped: 0]

Это результаты после запуска приложения в течение 10 минут. Как вы видите, String Deduplication была выполнена 452 раза и "дедуплицирована" 801.6 МБ. Stell Deduplication проверена 27 000 000. Когда я сравнил потребление памяти с Java 7 со стандартным Parallel GC до Java 8u20 с G1 GC и включил дедупликацию String, куча упала примерно на 50%:

Java 7 Параллельный GC

Java 7 Parallel GC

Java 8 G1 GC с дедупликацией строк

Java 8 G1 GC with String Deduplication

Ответ 3

Поскольку ваш первый вопрос уже был дан ответ, я отвечу на ваш второй вопрос.

Объекты String должны быть сопоставлены символом по символу, потому что хотя равный Object подразумевает равные хэши, инверсия не обязательно истинна.

Как Holger говорится в comment, это представляет собой хеш-столкновение.

Применяемые спецификации для метода hashcode() заключаются в следующем:

  • Если два объекта равны в соответствии с методом equals(Object), то вызов метода hashCode для каждого из двух объектов должен приводить к одному и тому же целочисленному результату.

  • Не требуется, чтобы, если два объекта неравны в соответствии с методом equals(java.lang.Object), то вызов метода hashCode для каждого из двух объектов должен производить различные целые результаты....

Это означает, что для гарантирования равенства сравнение каждого символа необходимо для подтверждения равенства двух объектов. Они начинают с сравнения hashCode, а не с помощью equals, поскольку они используют хеш-таблицу для ссылок, что повышает производительность.

Ответ 4

Стратегия, которую они описывают, состоит в том, чтобы просто повторно использовать внутренний массив символов одной строки в возможно много equal Strings. Нет необходимости, чтобы каждая строка имела свою собственную копию, если они равны.

Чтобы быстрее определить, равно ли 2 строки, хеш-код используется в качестве первого шага, так как это быстрый способ определить, являются ли строки равными. Отсюда их утверждение:

Как только он находит другую строку, которая имеет тот же хэш-код, она сравнивает их char с char

Это означает, что определенное (но более медленное) сравнение для равенства после возможного было определено с помощью хэш-кода.

В итоге, равные строки будут совместно использовать один базовый массив char.

Java долгое время имела String.intern(), чтобы сделать более или менее то же самое (например, сохранять память, дедуплицируя равные строки). Какой роман об этом заключается в том, что это происходит во время сбора мусора и может контролироваться извне.