Любой разработчик программного обеспечения, работавший с серверными приложениями корпоративного уровня на основе Java, сталкивался с этой неприятной странной ошибкой пользователя или инженера-испытателя: java.lang.OutOfMemoryError: Java heap space.
Чтобы понять это, мы должны вернуться к основам информатики алгоритмической сложности, особенно «пространственной» сложности. Если вспомнить, у каждого приложения есть характеристика наихудшего случая. В частности, с точки зрения размеров хранилища приложениям будет выделено больше места, чем рекомендуется, что является непредсказуемой, но острой проблемой. Это приводит к чрезмерному использованию памяти кучи, отсюда и состояние «недостаточно памяти».
Хуже всего в этой конкретной ситуации то, что приложение нельзя восстановить, и оно выйдет из строя. Любая попытка перезапустить приложение, даже с использованием максимальной памяти (опция -Xmx), не является долгосрочным решением. Стабильность использования памяти (то есть стабильность приложения) не может быть гарантирована без понимания того, что вызывает раздувание или заметное использование кучи. Так какой же более эффективный способ понять проблемы программирования с памятью? Когда память переполняется, понимание кучи памяти и распределения памяти приложения может ответить на этот вопрос.
Исходя из этой предпосылки, мы сосредоточимся на следующем:
-
Получить дамп кучи процесса Java при переполнении памяти.
-
Узнайте, с какими проблемами памяти сталкивается ваше приложение.
-
Используя профилировщик кучи, вы можете использоватьEclipse MATЭто отличный проект с открытым исходным кодом для анализа проблем с переполнением памяти.
Настройте приложение для подготовки к анализу кучи
Любая недетерминированная, спорадическая проблема, такая как переполнение памяти, является проблемой для посмертного анализа. Таким образом, лучший способ справиться с переполнением памяти — позволить виртуальной машине JVM создать дамп файла кучи состояния памяти виртуальной машины JVM.
У JVM Sun HotSpot есть способ дать указание JVM сбрасывать состояние кучи при нехватке памяти в файл. Его стандартный формат — .hprof. Итак, для этого добавьте XX:+HeapDumpOnOutOfMemoryError в элементы запуска JVM. Также необходимо добавить эту опцию в производственные системы, потому что переполнение памяти может занять много времени.
Если файл дампа кучи .hprof должен быть записан в определенном месте файловой системы, добавьте путь к каталогу в XX:HeapDumpPath . Просто убедитесь, что приложение всегда имеет доступ на запись к указанному пути к каталогу.
Анализ причин
101: понять природу ошибок нехватки памяти
При попытке оценить и понять ошибку переполнения памяти в первую очередь следует наблюдать за характеристиками роста памяти. Сделайте оценку вероятности в зависимости от ситуации:
-
Всплески: этот тип переполнения памяти может быть резким при определенных типах нагрузок. Приложение работает нормально, когда JVM выделяет память 20 пользователям. Однако, если вы достигнете 100-го пользователя, вы можете столкнуться с пиковым объемом памяти, что приведет к переполнению памяти. Есть два возможных пути решения этой проблемы.
-
Утечки: Использование памяти постепенно увеличивается с течением времени из-за некоторых проблем с программированием.
Здоровый граф с безвредным механизмом сборки мусора
График просочился с течением времени после того, как некоторое время был здоров
Графики памяти, которые вызывают скачки в использовании памяти, вызывают переполнение памяти
После того, как мы поймем природу проблемы с памятью, которая вызывает всплеск использования, и основываясь на выводах из анализа, можно использовать следующие методы, чтобы избежать ошибок нехватки памяти.
Устранение проблем с памятью
-
Исправить код, вызывающий переполнение памяти: пришлось исправить ошибку из-за того, что приложение постепенно добавляло объект в течение определенного периода времени без очистки его ссылок (ссылки на объекты из работающего приложения). Например, ошибка может заключаться в вставке в хэш-таблицу, где бизнес-объекты добавляются постепенно, но бизнес-логика и транзакция не удаляют эти объекты после завершения.
-
Увеличьте максимальный объем памяти в качестве исправления. После понимания характеристик рабочей памяти и кучи может потребоваться увеличить максимальный объем выделяемой памяти кучи, чтобы избежать повторного переполнения памяти, поскольку рекомендуемые максимальные значения памяти недостаточны для стабильности приложения. Поэтому приложению может потребоваться обновить информацию о флаге Java -Xmx до более высокого значения, а затем запустить его снова на основе оценки профилировщика кучи.
анализ кучи
Ниже мы подробно разберем, как использовать инструмент анализа кучи для анализа дампов кучи. В примере будет использоваться инструмент MAT с открытым исходным кодом от Eclipse Foundation.
Анализ кучи с помощью MAT
Пришло время глубокого погружения. Мы выполним ряд шагов, чтобы помочь изучить различные представления и представления в MAT, чтобы получить пример переполнения кучи и подумать об анализе.
1. Откройте файл кучи .hprof, созданный при возникновении ошибки нехватки памяти. Обязательно скопируйте файл дампа в специальную папку, так как MAT создаст много индексных файлов: File -> Open
2. Откройте файл дампа, есть опции для отчетов о подозрительных утечках памяти и отчетов о компонентах. Выберите Запустить отчет о предполагаемой утечке.
3. После того, как таблица с подозрениями на утечку будет открыта, круговая диаграмма в окне предварительного просмотра покажет распределение зарезервированной памяти для каждого объекта. Он показывает самые большие объекты в памяти (объекты с самой большой зарезервированной памятью — накопленная память и объекты, на которые ссылаются).
4. На приведенной выше круговой диаграмме показаны 3 подозреваемых путем агрегирования объектов с наибольшим количеством обращений к памяти (собственной и общей).
Давайте рассмотрим каждый случай, чтобы оценить, является ли это основной причиной ошибки нехватки памяти.
Подозрительный пункт 1
454 570 экземпляров «java.lang.ref.Finalizer», загруженных «», заняли 790 205 576 (47,96%) байтов.
Это говорит нам о том, что существует 454 570 экземпляров финализатора JVM, занимающих почти 50% выделенной памяти приложения.
Предполагая, что читатель знает, что делает Java Finalizer, что говорит нам приведенная выше информация?
Начало чтения:stackoverflow.com/questions/2…
По сути, разработчики пишут несколько пользовательских финализаторов для освобождения ресурсов экземпляра. Эти экземпляры, собранные финализаторами, выходят за рамки алгоритма сборки мусора JVM с использованием отдельных очередей. На самом деле этот путь длиннее, чем путь очистки механизма сборки мусора. Итак, теперь мы должны попытаться выяснить, что именно завершают эти финализаторы?
Также может быть подозрительным пункт 2, на который приходится 20% sun.security.ssl.SSLSocketImpl. Можем ли мы подтвердить, что эти экземпляры должны быть завершены финализатором?
Подозрительный пункт 2
Теперь давайте откроем вид Dominator под кнопками инструментов в верхней части MAT. Мы увидим все перечисленные экземпляры класса, проанализированные MAT, показывающие действительное хранилище кучи.
Далее, в представлении Dominator, мы пытаемся понять взаимосвязь между java.lang.Finalizer и sun.security.ssl.SSLSocketImpl. Щелкните правой кнопкой мыши столбец sun.security.ssl.SSLSocketImpl и откройте GC Roots -> исключить мягкие/слабые ссылки.
Теперь MAT начнет графически отображать память, чтобы показать путь к корню GC и соответствующую ссылку на экземпляр. Это будет отображаться на другой странице со следующей ссылкой:
Как показано в приведенной выше цепочке ссылок, экземпляр SSLSocketImpl поступает из java.lang.ref.Finalizer, и весь экземпляр SSLSocketImpl занимает около 88 КБ. Мы также заметили, что цепочка финализаторов представляет собой структуру данных списка, связанную выводами, которая указывает на следующий экземпляр.
вывод: На данный момент у нас есть четкое ощущение, что финализатор Java пытается собрать объекты SSLSocketImpl. Чтобы объяснить, почему до сих пор не собирается много информации, я начал проверять код.
Проверить код
Проверка кода должна увидеть, был ли закрыт сокет. В данном случае это показывает, что все потоки, связанные с вводом-выводом, должны быть правильно закрыты. В какой-то момент мы подозреваем, что JVM является инициатором. На самом деле в коде GC (сборщика мусора) Open JDK 6.0.XX есть ошибка.
Я надеюсь, что эта статья дала вам образец для анализа того, вызваны ли ошибки в Java-приложениях памятью в куче или внутренними проблемами. Надеюсь, вам понравится анализ кучи!
Расширенное чтение
Неглубокая куча против сохраненной кучи
Неглубокая куча — это память, потребляемая объектом. В зависимости от ситуации объект должен быть 32-битным или 64-битным (в зависимости от архитектуры операционной системы), 4 байта для целого числа, 8 байтов для типа Long и так далее. В зависимости от формата дампа кучи размер его памяти (например, выровненный до 8) может быть адаптирован для лучшего формирования реального потребления виртуальной машиной.
Сохраненный набор X — это набор объектов, которые будут удалены при сборке мусора X.
Сохраненная куча X — это сумма неглубоких куч всех объектов в сохраненном наборе X, то есть памяти, зарезервированной X.
В общем, мелкая куча объекта — это его размер в куче. Оставшийся размер того же объекта — это общий объем памяти кучи, когда объект удаляется сборщиком мусора.
Основной набор некоторых объектов, таких как все объекты определенного класса, или все объекты всех классов, загруженные определенным загрузчиком классов, или просто некоторые произвольные объекты, чей зарезервированный набор является, если они входят в основной набор Набор объектов, которые освобождается, когда все объекты становятся недоступными. Зарезервированный набор включает в себя эти объекты и другие объекты, доступные только через эти объекты. Размер набора хранения — это размер кучи, содержащей все объекты в наборе хранения.