предисловие
OutOfMemoryError
Я полагаю, что многие друзья сталкивались с проблемой.По сравнению с обычными бизнес-исключениями (выход за пределы массива, нулевой указатель и т. д.), такие проблемы трудно найти и решить.
Эта статья основана на обнаруженном недавно методе позиционирования и решения проблем онлайнового переполнения памяти; я надеюсь принести идеи и помочь студентам, столкнувшимся с подобными проблемами.
В основном из表现-->排查-->定位-->解决
Четыре шага для анализа и решения проблем.
внешний вид
В последнее время приложение на нашем производстве постоянно вылетает из памяти, и частота появления становится все выше и выше с ростом объема бизнеса.
Бизнес-логика программы очень проста: она использует данные из Kafka, а затем выполняет постоянные операции в пакетном режиме.
Феномен заключается в том, что чем больше сообщений в Kafka, тем быстрее будет частота исключений. Поскольку в то время были другие задания, операцию и обслуживание можно было только перезапустить, а состояние динамической памяти и GC следовало отслеживать.
Возобновление Дафа — это хорошо, но это не решает проблему в корне.
Проверять
Итак, мы хотим попытаться определить, в чем проблема, на основе данных памяти и журналов GC, собранных перед эксплуатацией и обслуживанием.
Оказывается, использование памяти в старом поколении остается высоким даже при использовании GC и со временем становится все выше.
В сочетании с журналом jstat обнаруживается, что даже если происходит FGC, старость не может быть восстановлена, и память достигла максимума.
Есть даже несколько приложений, где FGC достигал сотни раз, и время ужасно велико.
Это показывает, что использование памяти приложений, безусловно, является проблемой, есть много бесстыдных объектов не могут позволить себе всегда перерабатывать.
должность
Так как файл дампа памяти в продакшене очень большой, он достигает десятков ГБ. Это также связано со слишком большими настройками памяти.
Таким образом, использование анализа MAT занимает много времени.
Поэтому мы задались вопросом, можно ли его воспроизвести локально, что было бы гораздо лучше позиционировано.
Чтобы как можно быстрее воспроизвести проблему, я установил максимальную память кучи локального приложения на 150 МБ.
Затем при использовании Kafka Mock постоянно генерирует данные для цикла while.
В то же время, когда приложение запускается, используйте VisualVM для подключения к приложению, чтобы отслеживать использование памяти и GC в режиме реального времени.
Результат работал 10 минут и проблем с использованием памяти не было. Как видно из рисунка, каждый раз, когда генерируется память GC, ее можно эффективно перерабатывать, поэтому повторения проблемы не происходит.
Трудно локализовать проблему, не имея возможности воспроизвести ее. Итак, мы просмотрели код и обнаружили, что производственная логика отличается от фиктивных данных цикла while.
Глядя на производственный журнал, обнаруживается, что из Kafka каждый раз извлекаются сотни фрагментов данных, и мы можем генерировать только каждый раз, когда мы Mockодин.
Чтобы запустить как можно больше, запустите программу производителя на сервере, и данные будут непрерывно отправляться в Kafka.
Конечно же, память не выдерживает после работы более минуты.Посмотрите на левое изображение и обнаружите, что частота GC очень высока, но восстановление памяти ничтожно мало.
При этом фон тоже начал распечатываться из памяти, из-за чего проблема повторялась.
решать
Судя по текущей производительности, в памяти много объектов, на которые всегда сильно ссылались и которые не могут быть переработаны.
Поэтому я хочу посмотреть, какие объекты занимают так много памяти, и использовать функцию HeapDump VisualVM для немедленного дампа памяти текущего приложения.
оказатьсяcom.lmax.disruptor.RingBuffer
Типовые объекты занимают почти 50% памяти.
Когда я увидел эту сумку, я подумал о ней.Disruptor
круговая очередь.
Еще раз просмотрели код и обнаружили, что 700 фрагментов данных, извлеченных из Kafka, были напрямую потеряны для Disruptor.
Это также может объяснить, почему первые данные моделирования не воспроизводили проблему.
При моделировании в очередь ставится один объект, а в производственной ситуации в очередь ставится 700 единиц данных. Этот объем данных составляет 700-кратный разрыв.
Disruptor, как циклическая очередь, существует до тех пор, пока объект не будет перезаписан.
Я тоже провел эксперимент, и он оказался правдой.
Я устанавливаю размер очереди на 8 и записываю 10 фрагментов данных от 0 до 9. Когда записывается 8, предыдущая позиция 0 будет перезаписана и т. д. (аналогично позиционированию по модулю HashMap).
Допустим, в продакшене наша очередь имеет размер 1024, тогда по мере работы системы мы обязательно получим 1024 локации, заполненные объектами, а каждая локация — 700!
Итак, я проверил конфигурацию RingBuffer Disruptor в действии, и результат был таким:1024*1024
.
Порядок величины очень страшен.
Чтобы проверить, не в этом ли проблема, я локально заменил значение на 2 и попробовал минимальное значение.
Те же 128 МБ памяти также непрерывно извлекают данные через Kafka. Мониторя следующим образом:
После 20-минутной работы система работает нормально, каждый раз сборщик мусора может восстановить большую часть памяти, и в конечном итоге она выглядит зазубренной.
Таким образом, проблема найдена, но конкретная установка этого значения в продакшене может быть известна только путем тестирования в соответствии с бизнес-ситуацией, а исходное 1024*1024 больше никогда не может быть использовано.
Суммировать
Хотя я изменил строку кода в конце (она не была изменена, я напрямую изменил конфигурацию), но я думаю, что этот процесс устранения неполадок имеет смысл.
Это также даст большинству студентов, которые считают, что черный ящик, такой как JVM, трудно начать с интуитивного чувства.
同时也得感叹 Disruptor 东西虽好,也不能乱用哦!
Посмотреть связанный демонстрационный код:
Ваши лайки и ретвиты — лучшая поддержка.