Автор оригинала: Ram Lakshmanan.
Не все истории должны быть историями успеха. Реальность тоже не такова. Мы хотели бы поделиться правдивой, разочаровывающей историей (но феноменальным опытом обучения), которая может быть полезна для вас.
Это история об оптимизации использования памяти веб-приложения. Это приложение было настроено с большим объемом памяти (4 ГБ) только для обслуживания нескольких транзакций в секунду. Таким образом, мы решили изучить закономерности использования памяти этого приложения. Мы захватили дампы кучи этого приложения с помощью инструмента “jmap”. Мы загрузили захваченный дамп кучи в инструмент HeapHero. Heap Hero-это инструмент анализа дампа кучи, такой же, как Eclipse MAT, JProfiler, Yourkit. Инструмент “Герой кучи” профилировал память и предоставлял статистику по общим классам, общим объектам, размеру кучи, представлению гистограммы больших объектов, находящихся в памяти. В дополнение к этим традиционным показателям HeapHero сообщил об общем объеме памяти, потраченной впустую из-за неэффективных методов программирования. В современных вычислениях значительный объем памяти тратится впустую из-за неэффективных методов программирования, таких как: создание дубликатов объектов, неоптимальные определения типов данных (объявление “двойных” и назначение только “плавающих” значений), чрезмерное распределение и недостаточное использование структур данных и ряд других методов.
Это приложение не было исключением из этого правила. Герой кучи сообщил, что приложение тратит 56% памяти из-за неэффективных методов программирования. Да, это поднимает брови на 56%. Он сообщил, что 30% памяти приложения тратится впустую из-за повторяющихся строк.
Рис.: Инструмент HeapHero отчет об объеме памяти, потраченной впустую из-за неэффективного программирования
Дедупликация строк После обновления Java 8 20 был введен новый аргумент JVM “- XX:+UseStringDeduplication”. Когда приложение запускается с этим аргументом, JVM удалит повторяющиеся строки из памяти приложения во время сборки мусора. Однако, пожалуйста, имейте в виду, что аргумент ‘-XX:+UseStringDeduplication’ будет работать только с алгоритмом G1 GC. Вы можете активировать алгоритм G1 GC, передав ‘-XX:+UseG1GC’.
Мы пришли в восторг. Мы думали, что, просто введя аргумент JVM ‘-XX:+UseG1GC-XX:+UseStringDeduplication’, мы сможем сэкономить 30% памяти без какого-либо рефакторинга кода. Ух ты, разве это не замечательно? Чтобы проверить эту теорию, мы провели два разных теста в нашей лаборатории производительности:
Тест 1: Прохождение ‘-XX:+UseG1GC’
Тест 2: Прохождение ‘-XX:+UseG1GC -XX:+UseStringDeduplication’
Мы включили журналы сбора мусора в приложении, чтобы изучить схему использования памяти. Проанализированы журналы сбора мусора с помощью бесплатного онлайн – инструмента анализа журналов сбора мусора – GCeasy . Мы надеялись, что в тестовом запуске № 2 мы сможем увидеть сокращение потребления памяти на 30% из-за устранения повторяющихся строк. Однако реальность была совершенно иной. Мы не видели никакой разницы в использовании памяти. Оба тестовых запуска последовательно показывали одинаковый объем использования памяти. Смотрите графики использования кучи, созданные инструментом GC easy, путем анализа журналов сбора мусора.
Рис.: График простого использования кучи GC с ‘-XX:+UseG1GC’
Рис.: График простого использования кучи GC с ‘-XX:+UseG1GC -XX:+UseStringDeduplication’
In Test run #1 heap usage hovering around 1500mb all through the test, in test run #2 also heap usage was hovering around 1500mb. Disappointingly we didn't see the anticipated 30% reduction in the memory usage, despite introducing '-XX:+UseG1GC -XX:+UseStringDeduplication' JVM arguments.
Почему не произошло сокращения использования кучи? “Почему не произошло сокращения использования кучи?” – этот вопрос действительно озадачил нас. Правильно ли мы настроили аргументы JVM? Разве “- XX:+UseStringDeduplication ” не выполняет свою работу правильно? Является ли отчет об анализе с помощью инструмента GC easy правильным? Все эти вопросы тревожили наш сон. После детального анализа мы выяснили горькую правду. По-видимому, “- XX:+UseStringDeduplication ” устранит дубликаты строк, которые присутствуют только в памяти старого поколения ☹ . Это не устранит повторяющиеся строки в молодом поколении. Память Java имеет 3 основные области: молодое поколение, старое поколение, метапространство. Вновь созданные объекты переходят к молодому поколению. Объекты, которые сохранились в течение более длительного периода, передаются в старое поколение. Объекты, связанные с JVM, и информация о метаданных хранятся в метапространстве. Таким образом, другими словами, “- XX:+UseStringDeduplication ” удалит только повторяющиеся строки, которые живут в течение более длительного периода. Поскольку это веб-приложение, большинство строковых объектов были созданы и немедленно уничтожены. Это было очень ясно из следующих статистических данных, приведенных в отчете об анализе журнала обращений:
Рис.: Статистика создания/продвижения объектов, представленная GC easy
Средняя скорость создания объектов этого приложения составляет: 44,93 Мб/сек, в то время как средняя скорость продвижения (т. е. от молодого поколения к старому поколению) составляет всего 918 кб/сек. Показательно, что очень малая часть объектов является долгоживущими. Даже в этих продвигаемых объектах со скоростью 918 кб/сек строковые объекты будут составлять меньшую часть. Таким образом, количество повторяющихся строк, удаленных с помощью ‘-XX:+UseStringDeduplication’, было очень незначительным. Таким образом, к сожалению, мы не увидели ожидаемого сокращения объема памяти.
Заключение (а). ‘ -XX:+UseStringDeduplication’ будет полезен только в том случае, если в приложении много долгоживущих повторяющихся строк. Это не дало бы плодотворных результатов для приложений, когда большинство объектов недолговечны. К сожалению, большинство современных веб-приложений, объектов приложений микросервисов, недолговечны.
(b). Еще одним известным вариантом, рекомендуемым в отрасли для устранения дубликатов строк, является использование функции String#intern (). Однако строка#intern() не будет полезна для этого приложения. Потому что в String#intern() вы в конечном итоге создаете строковые объекты, а затем сразу же удаляете их. Если строка по своей природе недолговечна, вам не нужно делать этот шаг, так как обычный процесс сбора мусора приведет к удалению строк. Кроме того, строка#intern() имеет возможность добавить (очень небольшие) накладные расходы на задержку к транзакции и нагрузке на процессор.
(c). Учитывая текущую ситуацию, лучший способ удалить дубликаты строк из приложения-это провести рефакторинг кода, чтобы убедиться, что дубликаты строк даже не создаются. Герой кучи указывает пути кода, в которых создается множество дубликатов строк. Используя эти указатели, мы продолжим наше путешествие по рефакторингу кода, чтобы уменьшить потребление памяти.
Оригинал: “https://www.codementor.io/@suryab/disappointing-story-on-memory-optimization-qlp9tpe60”