Руководство по управлению памятью Java (и спасению вашего кода)

Примечание переводчика: желание перевести заметку появилось ранним июньским утром после прочтения ее в полусонном состоянии в вагоне метро. Целевая аудитория: люди, делающие первые шаги в мире Java и по роду своего основного технического бэкграунда или желания сильно жаждут забраться под капот Java и изучить все «электродинамические» процессы. Уверен, что для прочитавших это будет отправной точкой путешествия в мир настройки JVM и GC. Попутного ветра!
Оригинал статьи лежит здесь


Будучи разработчиком, вы проводите бесчисленные часы за вычищением багов из Java-приложения и достижением производительности в тех местах где нужно. Во время тестирования замечаете, что приложение постепенно работает медленней, и в конце полностью заваливается или просто демонстрирует плохую производительность. В конечном счете признаете, что происходят утечки памяти. Garbage collector Java делает всё что может, чтобы справиться с этими утечками. Но есть только много вещей, которые можно выполнить, когда сталкиваетесь с ситуациями подобными этим. Вам нужны способы идентификации вызовов утечек памяти, выявления причин и понимания роли Java garbage collector'а влияющего на общую производительность приложения.


Основные симптомы утечек памяти Java

Встречаются несколько симптомов указывающих, что приложение имеет проблемы утечки памяти. Небольшое снижение производительности, а не внезапный отказ работы приложения, указывает только на утечку памяти. Проблема может случаться каждый раз во время работы или только тогда, когда приложение начинает работать с большим объемом данных или, напротив, вы начинаете масштабировать приложение. Приложение, возможно, покажет ошибку нехватки памяти, как только утечка сожрет все доступные ресурсы памяти. Перезапустив приложение и надеясь на лучшее, натолкнетесь неоднократно на аварийные завершения до того момента, пока утечка не будет устранена. В общем, утечки памяти случаются когда ссылки на объекты накапливаются, вместо того, чтобы освобождать память. Они занимают всю доступную память и делают для приложения невозможным доступ до нужных ему ресурсов.

Ошибки конфигурации выглядящие как утечки памяти

Перед тем, как заглянете в ситуации вызывающие проблемы с памятью Java и проведете анализ, необходимо убедиться, что исследования не имеют отношения к абсолютно другой задаче. Часть ошибок out-of-memory возникают из-за различных ошибок, например ошибок конфигурации. У приложения, возможно, недостаток памяти в куче или оно конфликтует в системе с другими приложениями. Если начинаете говорить о проблемах нехватки памяти, но не можете определить что вызывает утечку, взгляните на приложение по-другому. Обнаружится, что нужно сделать изменения в потоке финализации или увеличить объем permanent generation пространства, являющегося областью памяти JVM для хранения описания классов Java и некоторых дополнительных данных.

Преимущества инструментов мониторинга памяти

Инструменты мониторинга памяти дают бОльшую видимость использования доступных ресурсов приложением Java. Используя данное ПО, вы делаете шаг для сужения поиска корня проблемы утечки памяти и прочих инцидентов связанных с производительностью. Инструменты идут в нескольких категориях, и вам, возможно, нужно использовать множество приложений, чтобы разобраться как начать правильно обозначать проблему и что пошло не так, даже если вы имеете дело с утечками памяти.
Heap dump (дампа кучи) файлы дают необходимые сведения для анализа Java-памяти. В этом случае вам нужно использовать два инструмента: один для генерации дамп-файла и другой для подробного анализа. Такое решение дает детализированную информацию о том, что происходит с приложением. Один раз инструмент указывает места возможных проблем и работает над сужением площади, чтобы обнаружить точное место возникновения инцидента. И этот период времени — время самой длинной и портящей настроение части проб и ошибок. Анализатор памяти указывает несколько проблем в коде, но вы не уверены абсолютно, с какими проблемами столкнулось ваше приложение. Если всё ещё сталкиваетесь с прежней ошибкой, начните сначала и поработайте над другой возможной ошибкой.
Сделайте одно изменение за раз и попытайтесь продублировать ошибку. Нужно будет дать приложению поработать некоторое время, чтобы продублировать условия возникновения ошибки. Если при первом тесте происходит утечка памяти, не забудьте протестировать приложение под нагрузкой. Приложение может работать отлично с небольшим количеством данных, но может снова выбросить прежние ошибки при работе с большим объемом данных. Если еще возникает всё та же самая ошибка, нужно начать сначала и разобрать другую возможную причину.
Инструменты мониторинга памяти доказывают свою пользу после того, когда приложение стало полностью работающим. Можно удаленно наблюдать за производительностью JVM и проактивным обнаружением сбойных ситуаций перед тем, как разработчик погрузится в проблему и будет собирать исторические данные производительности, чтобы помочь себе в будущем улучшить техники программирования и наблюдать как Java работает под тяжелой нагрузкой. Многие решения включают режимы оповещения «опасность» или другие подобные режимы и разработчик сразу может знать, что происходит не так, как хотелось. Каждый разработчик не хочет, чтобы критическое приложение, будучи в промэксплуатации, падало и являлось причиной потери десятков или сотен тысяч долларов во время простоя приложения, поэтому инструменты мониторинга памяти уменьшают время реагирования разработчика. Приложения мониторинга памяти дают начать процесс диагностики мгновенно, вместо того, чтобы попросить вас пойти к заказчику, где никто не скажет какая именно ошибка случилось или какой код ошибки выдало приложение.
Если часто погружаетесь в проблемы памяти и производительности вашего Java-приложения, плотно возьмитесь за процесс тестирования. Обозначьте каждую слабую область в процессе разработки и измените стратегии тестирования. Посоветуйтесь с коллегами и сравните свои подходы тестирования с существующими лучшими практиками. Иногда вам надо пересмотреть маленький фрагмент кода и далее обеспечить длительное воздействие на все приложение.

Роль Garbage Collector на память Java и утечки памяти

Garbage Collector (cборщик мусора) в Java играет ключевую роль в производительности приложения и использования памяти. Он ищет неиспользуемые (мертвые) объекты и удаляет их. Эти объекты больше не занимают память, так что ваше приложение продолжает обеспечивать доступность ресурсов. Иногда приложение не дает GC достаточно времени или ресурсов для удаления мертвых объектов и они накапливаются. Можно столкнуться с такой ситуацией когда идет активное обращение к объектам, которые, вы полагаете, мертвы. Сборщик мусора не может сделать ничего c этим, т.к. его механизм автоматизированного управления памяти обходит активные объекты. Обычно сборщик мусора работает автономно, но необходимо настроить его поведение на реагирование тяжелых проблем с памятью. Однако, GC может сам приводить к проблемам производительности.

Области GC

Сборщик мусора для оптимизации сборки разделяет объекты на разные области. В Young Generation представлены объекты, которые отмирают быстро. Сборщик мусора часто работает в этой области, с того момента, когда он должен проводить очистку. Объекты оставшиеся живыми по достижению определенного периода переходят в Old Generation. В области Old Generation объекты остаются долгое время, и они не удаляются сборщиком так часто. Однако, когда сборщик работает в области, приложение проходит проходит через большую операцию, где сборщик смотрит сквозь живые объекты для очистки мусора. В итоге объекты приложения находятся в конечной области permanent generation. Обычно, эти объекты включают нужные метаданные JVM. Приложение не генерирует много мусора в Permanent Generation, но нуждается в сборщике для удаления классов когда классы больше не нужны.

Связь между Garbage Collector и временем отклика

Сборщик мусора, независимо от приоритета исполнения потоков приложения, останавливает их не дожидаясь завершения. Такое явление называется событием «Stop the World». Область Young Generation сборщика мусора незначительно влияет на производительность, но проблемы заметны, если GC выполняет интенсивную очистку. В конечном итоге вы оказываетесь в ситуации, когда минорная сборка мусора Young Generation постоянно запущена или Old Generation переходит в неконтролируемое состояние. В такой ситуации нужно сбалансировать частоту Young Generation с производительностью, которая требует увеличение размера этой области сборщика.
Области Permanent Generation и Old Generation сборщика мусора значительно влияют на производительность приложения и использования памяти. Эта операция major очистки мусора проходит сквозь heap, чтобы вытолкнуть отмершие объекты. Процесс длится дольше чем minor сборка и влияние на производительность может идти дольше. Когда высокая интенсивность очистки и большой размер области Old Generation, производительность всего приложения увязывает из-за событий «Stop the world».
Оптимизация сборки мусора требует мониторинга как часто программа запущена, влияния на всю производительность и способов настройки параметров приложения для уменьшения частоты мониторинга. Возможно нужно будет идентифицировать один и тот же объект, размещенный больше, чем один раз, причем приложению не нужно отгораживаться от размещения или вам надо найти точки сжатия, сдерживающие всю систему. Получение правильного баланса требует уделения близкого внимания ко всему от нагрузки на CPU до циклов вашего сборщика мусора, особенно если Young и Old Generation несбалансированы.
Адресация утечек памяти и оптимизация сборки мусора помогает увеличить производительность Java-приложения. Вы буквально жонглируете множеством движущихся частей. Но с правильным подходом устранения проблем и инструментами анализа, спроектированных чтобы дать строгую видимость, вы достигнете света в конце туннеля. В противном случае замучаетесь с возникающими неполадками связанных с произодительностью.
Тщательное размещение памяти и её мониторинг играют критическую роль в Java-приложении. Необходимо полностью взять в свои руки взаимодействие между сборкой мусора, удалением объектов и производительностью, чтобы оптимизировать приложение и избежать ошибок упирающихся в нехватку памяти. Инструменты мониторинга дают оставаться на высоте, чтобы обнаружить возможные проблемы и обозначить тенденции утилизации памяти так, что вы принимаете проактивный подход к исправлению неисправностей. Утечки памяти часто показывают неэффективность устранения неисправностей обычным путем, особенно если вы сталкиваетесь с неверными значениями параметров конфигурации, но решения вопросов связанных с памятью помогают быстро избежать инцидентов стоящих у вас на пути. Совершенство настройки памяти Java и GC делают ваш процесс разработки намного легче.

6 комментариев

Smetchik
Какова проза жизни! Java и утечки памяти?! Помнится в книгах по Java, говорится, что язык избавлен от недостатков C++, таких как управление памятью и утечки памяти :)
pandaFromMinsk
Спасибо за комментарий. Цель статьи была не показать, что можно после прочтения материала побороть все outOfMemory. :-)
Exidnus
Что за книги?
Smetchik
Например
Герберт Шилд
Глава 1. История и развития языка Java
«Например в среде С/С++ программист должен вручную резервировать и освобождать всю динамически распределяемую память. Иногда это ведет к возникновению трудностей, поскольку программисты забывают освободить ранее зарезервированную память или, что еще хуже, пытаются освободить область памяти, все еще используемую другой частью кода. Java полностью исключает такие ситуации, автоматически управляя резервированием и осовобождением памяти» Кажется в первой части описывается утечка памяти, в второй части говорится о манне небесной :)

Язык программирования JavaSE 8 Гослинг Д. и прочие
Глава 1. Введение
«Сюда входят автоматическое управление памятью, обычно с использованием сборщика мусора(чтобы избежать проблемм, связанных с явным освобождением памяти, как при вызовах free а с или delet в С++)»
Как бы намекают, что проблем с памятью у вас не будет, правда похоже?

Патрик Нимейер
Программирование на Java
Глава 1
Современный язык
Раздел Управление динамической памятью
(не буду цитировать найдете если интереcно, там тоже вроде как описывается что Java как бы решает в сравнении с C++/C в проблемах с памятью).
Я нисколько не ущемляю достоинств Java, но как бы многие авторы намекают, что в отличи там от C++/C вы уйдете от головной боли памятью, а прочитав эту статью, мы как бы приходим к тому, что используя Java можем получить некоторые проблемы :)
EvIv
Спасибо за труд, многим, кто не знает английского, полезно будет.
Однако позволю себе немного критики: читать очень сложно, много «нерусских» конструкций, как будто статью просто слегка подчистили после гугл-транслейта (надеюсь, что это не так). Предложения типа «Heap dump (дампа кучи) файлы дают необходимые сведения для анализа Java-памяти.» выглядят, будто в английском предложении перевели все слова и согласовали падежи. В русском языке принят совсем другой порядок слов в предложении и словосочетаниях.
Не считайте за личное нападение, я просто хочу помочь указать на неочевидные для переводчика моменты. Если вы захотите делать свои переводы лучше, неплохо было бы после первого «прохода» перечитывать свой текст и перефразировать те части, которые кажутся «корявыми». Это, кстати, в большинстве случаев отнимает больше сил, чем непосредственно первый перевод, зато читать его будет гораздо легче.
pandaFromMinsk
За конструктивную критику я очень благодарен. Просто у самого проблемы с постановкой слога и в русском языке, да и уже давно привычнее читать в оригинале тексты из блогов и топики на StackOverflow и общаться с коллегами по всяким issues, tasks исключительно на айтишном жаргоне. Полагаю, что нужно развивать навык написания русскоязычных художественных текстов.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.