No Image

Что такое утечка памяти

СОДЕРЖАНИЕ
0 просмотров
22 января 2020

В статье приводится описание способов вычленения такой проблемы как утечка оперативной памяти. Здесь вы ознакомитесь с тем, как убедиться в том, что утечка по памяти присутствует в Windows, и в чём вероятная причина утечки.

Утечка оперативной памяти всегда самый настоящий кошмар для каждого из пользователей. Но среди них прежде всего эта проблема бьёт по разработчикам с одной стороны и по владельцам серверов с другой. Первые сначала разрабатывают “кривое” приложение, на которое впоследствии сыпется куча рекламаций, а вторые мучаются, страдая от лавинообразных потерь в оперативке, в конце концов перезагружая сервера с кучей извинений в адрес клиентов.

Утечка оперативной памяти: и сразу к делу

Первое, в чём стоит убедиться, что в периодических “фризах” Windows виновата действиьельно утечка оперативной памяти. И у вас, вероятно, уже есть подозрение на какой-то конкретный процесс. Мы, обычные пользователи Windows, привыкли использовать для подобных проблем Диспетчер задач. Он, к слову, от версии к версии становится всё удобнее в смысле анализа и вероятных выводов в сторону системных вопросов. Но в свете нашей проблемы, даже развернув столбцы распределения RAM Диспетчера по-максимому…

много полезного из окна мы всё равно не увидим. Дело в том, что Диспетчер задач Windows традиционно отображает рабочий набор памяти (т.е. выделяемой). Но не объёмы реально используемой. В чём разница? Проще говоря, некоторая часть из рабочего набора памяти может работать сразу на несколько приложений . Этот тип памяти получил название Разделяемая (Shared Memory). И вот здесь и начинаются проблемы. И всё потому, что рабочий набор памяти может быть ГОРАЗДО БОЛЬШЕ, чем объём памяти используемой.

Причины утечки

К более результативным способам как обнаружить утечку памяти мы ещё вернёмся, а пока посмотрим в корень проблемы. Профессионалы предлагают разделить путь к ней на три вопроса:

  • нужно определиться с типом утечки – является ли она управляемой или неподконтрольна
  • в чём истинная причина утечки (конечный объект, незакрытый файл какой-то библиотеки и т.п.)
  • что за функция или маршрут вызывают утечку?

Кроме того, утечки оперативной памяти вообще принято разделять на две группы:

  • утечка в системном ядре (Kernel Mode)
  • утечка на уровне пользователя (User Mode)

Исходя из вариантов маршрута, отслеживающего причину и тип утечки, мы можем выбрать вариант как безошибочно определить “пакостное” приложение.

Утечка оперативной памяти: а вам не показалось?

Чтобы более-менее точно определить размер памяти, которая расходуется программой, нужно отслеживать байты исключительного пользованиязакрытые байты памяти (private bytes) для приложения. Этот счётчик будет считать объём памяти, которое потребляется именно приложением; совместной памятью они не будут использоваться. С них и начнём. Закрытые байты – это те участки памяти, которые системой остальным программам не предлагаются; на них наложено своего рода табу. А вот чтобы посчитать эти объёмы, нам уже не обойтись без средств оценки производительности. Далеко ходить не нужно, для этих целей отлично подойдёт Системный монитор Windows . Чтобы начать работу:

  • запускаем подозрительный процесс и оставляем его фоном
  • в строке поиска вводим быструю команду Windows
  • выбираем слева Системный монитор и удаляем автоматические счётчики, щёлкнув по красному крестику

  • в освобождённом квадранте правой мышкой выбираем Добавить счётчики…

Если я подозреваю, например, процесс chrome.exe в причине утечки, мне нужно его найти и выделить среди остальных. Выбираю счётчик Процесса

а затем подсчёт Байт исключительного пользования. Нам осталось подобрать само имя процесса. Если вы хотите выбрать несколько, зажимайте Shift: при выборе. Заканчиваем кнопкой Добавить>> и смотрим:

Сразу рекомендую в Свойствах счётчиков выставить время перехвата от 600 сек (если есть время, можно и гораздо больше).

Сейчас появятся данные по нагрузке на оперативную память со стороны выбранной программы. Моментального результата не будет: иногда приходится ждать часами, пока опасения не подтвердятся. А “подтвердятся” выглядят примерно так:

Скачкообразное увеличение потребления свидетельствует о проблеме в связанных с процессом файлах. Проблема такого способа одна – такой картинки возможно “придётся” ждать часами, а то и днями. И это в течение единовременного сеанса. Сейчас себе такое может позволить далеко не всякий администратор. Так что этот способ (вполне себе официально документируемый) хорош либо для приложений с явными дефектами, которые сразу вешают систему в течение короткого времени, либо для “посмотреть наглядно, как оно работает”. Иногда помогает добавление ещё одного счётчика – Байт виртуальной памяти . Это показатель текущего размера виртуального адресного пространств, которое используется нашим приложением. Так вот, сравнение показаний может привести к таким выводам:

  • утечка оперативной памяти выражается в постоянном увеличении числа байт исключительного пользования именно в файлах данных
  • утечки по остальным приложениям и программам – это разрастание виртуального пространства.

Однако процесс поиска можно под-ускорить или под-уточнить. Попробуйте добавить ещё несколько счётчиков. Проверьте, что подозрительные процессы запущены. А среди новых счётчиков найдите:

  • Память ->Байт в выгружаемом страничном пуле
  • Память ->Байтв невыгружаемом страничном пуле
  • Файл подкачки ->% использования

В свойствах счётчиков выставляем длительность 600 сек., форма вывода – Отчёт:

Попейте чайку, пока счётчики не разгонятся. Через указанное время можно сделать предварительные выводы. А они заключаются в следующем:

  • утечки пользовательского режима выглядят как непрерывно увеличивающиеся показатели миллионов байт по строке Байт в выгружаемом страничном пуле. Это влечёт и увеличение в строке % использования Файла подкачки;
  • утечки в режиме ядра проявляются в строке Байт в невыгружаемом страничном пуле. Хотя и второй показатель Памяти тоже может “страдать”. Однако. Учитывая то, что приложения время от времени память кэшируют, величины могут изменяться в незначительном диапазоне (туда-сюда). Это нормально. Но если кривая ползёт неуклонно вверх, у вас налицо утечка оперативной памяти.

В одну статью диагностику и решение мне запихнуть не удалось. Так что в следующей статье рассмотрим варианты как попытаться победить утечку в зависимости от источника проблемы: ядро или пользователь?

В статье мы расскажем, что такое утечка памяти, как происходит и какие вызывает последствия для операционной системы Android. Также рассмотрим инструменты для обнаружения утечек памяти, типовые модели утечки памяти в Android, способы оценки степени критичности и методы предотвращения основных видов утечек.

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

Что такое сборщик мусора?

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

По иронии судьбы сборщик мусора Java работает слишком хорошо, создавая и удаляя большое количество объектов. Большинство проблем управления памятью решаются, но часто за счет уменьшения производительности. Создание универсального сборщика мусора, применяемого ко всем возможным ситуациям, привело к сложностям с оптимизацией системы. Чтобы разобраться со сборщиком мусора, нужно сначала понять, как работает управление памятью на виртуальной машине Java (JVM).

Как работает сборщик мусора

Многие считают, что сборщик мусора собирает и удаляет из памяти неиспользуемые объекты. На самом деле сборщик мусора Java делает все наоборот. Живые объекты отмечаются как активные, а все остальное считается мусором. Как следствие, эта фундаментальная особенность может привести ко многим проблемам с производительностью.

Начнем с так называемой кучи (англ. «heap») — области памяти, используемой для динамического распределения ресурсов приложений. В большинстве конфигураций операционная система заранее отдает эту часть под управление JVM во время работы программы. Это приводит к последствиям:

  • создание объекта происходит быстрее, потому что глобальная синхронизация с операционной системой не требуется для каждого отдельного объекта. В процессе выделения памяти под приложение JVM просто фиксирует за задачей определенный участок памяти и перемещает указатель смещения вперед (картинка ниже). Следующее распределение начинается с этого смещения и занимает следующий участок памяти;
  • когда объект больше не используется, сборщик мусора восстанавливает базовое состояние этого участка памяти и повторно использует ее для размещения другого объекта. Это означает, что нет явного удаления и память все еще не будет очищена.
Читайте также:  Топ игровых механических клавиатур

Новые объекты просто размещаются в конце кучи.

Все объекты размещены в куче, управляемой JVM. Каждый элемент, используемый разработчиком, обрабатывается таким образом, включая объекты класса, статические переменные и даже сам код. Пока объект ссылается на что-то, JVM считает его используемым. Когда объект больше не ссылается и, следовательно, недоступен по коду приложения, сборщик мусора удаляет его и восстанавливает неиспользуемую память. Все настолько просто, как и звучит, но возникает вопрос: какова первая ссылка в дереве объектов?

Корни сборщика мусора — начальная позиция всех иерархий (деревьев) объектов

Каждое дерево объектов должно иметь один или несколько корневых объектов. Пока приложение может достичь этих корней, все дерево доступно. Но когда эти корневые объекты считаются доступными? Специальные объекты, называемые корнями сборщика мусора (корни GC, рисунок ниже), всегда доступны, а также любой объект, чьим корнем является корень сборщика мусора.

В Java существуют следующие типы корней сборщика мусора:

  • локальные переменные поддерживаются активными благодаря стеку потока. Это фиктивная виртуальная ссылка на объект и, следовательно, она не видна. Для всех целей и задач локальные переменные являются корнями сборщика мусора;
  • активные потоки Java всегда считаются используемыми объектами и поэтому являются корнями сборщика мусора. Это особенно важно для локальных переменных потока;
  • на статические переменные ссылаются их классы. Это делает их де-факто корнями сборщика мусора. Сами классы могут быть собраны сборщиком, что приведет к удалению всех статических переменных, на которые они ссылаются. Это имеет особо важно, когда мы используем серверы приложений, контейнеры OSGi или загрузчики классов в целом.

Корни сборщика мусора — это объекты, которые ссылаются на JVM и, таким образом, остаются в памяти устройства.

Поэтому простое Java-приложение имеет следующие корни сборщика мусора:

  • локальные переменные в главном методе;
  • основной поток;
  • статические переменные главного класса.

Маркировка и сборка мусора

Чтобы определить, какие объекты больше не используются, JVM периодически запускает алгоритм маркировки и сборки мусора:

  1. Алгоритм «проходит» по всей иерархии объектов, начиная с корней сборщика мусора, и отмечает каждый найденный объект как активный.
  2. Вся участки памяти, не содержащие активных объектов (а точнее объектов, которые не были отмечены в предыдущем шаге), восстанавливаются. Они просто обозначаются как свободные.

Сборщик мусора предназначен для устранения причины утечки памяти — недостижимых, но не удаленных объектов в памяти. Однако это работает только для утечек памяти в классическом их понимании. Возможно, что неиспользуемые объекты по-прежнему доступны приложению, потому что разработчик просто забыл очистить ссылки на них. Такие объекты не могут быть собраны сборщиком. Хуже того, такая логическая утечка памяти не может быть обнаружена никаким программным обеспечением.

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

Простыми словами, в памяти остаются только те объекты, которые используются пользователем.

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

Почему утечка памяти — это плохо?

Ни один объект не должен оставаться в памяти дольше, чем нужно. Ведь эти ресурсы могут пригодиться для задач, которые могут иметь реальную ценность для пользователя. В частности, для Android это вызывает следующие проблемы:

Во-первых, когда происходят утечки, доступной для использования памяти становится меньше, что вызывает более частые запуски сборщика мусора. Такие запуски останавливают рендеринг пользовательского интерфейса, а также вызывают остановку других компонентов, необходимых для нормальной работы системы. В таких случаях прорисовка кадра длиться дольше обычных 16 мс. Когда прорисовка опускается до отметки ниже 100 мс, пользователи начнут замечать замедления в работе приложений.


В Android отзывчивость приложений контролируется менеджером активности и менеджером окон. Система откроет диалог ANR (приложение не отвечает) для конкретного приложения, когда будет выполнено одно из следующих условий:

  • приложение не отвечает на нажатие клавиш, или нажатия на экран на протяжении 5 секунд;
  • BroadcastReceiver не завершился на протяжении 10 секунд;

Вряд ли пользователям понравится видеть это сообщение на экранах своего гаджета.

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

Как определить утечку?

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

Приложение Leak Canary от Square — хороший инструмент для обнаружения утечек памяти в приложении. Оно создает ссылки на объекты вашего приложения и проверяет, удаляются ли эти ссылки сборщиком мусора. Если нет, тогда все данные записываются в файл .hprof и проводится анализ на наличие утечек памяти. Если утечка все же будет обнаружена, приложение пришлет вам уведомление о том, как это происходит. Рекомендуется использовать это приложение до выпуска в продакшн.Android Studio также имеет удобный инструмент для обнаружения утечек памяти. Если есть подозрения, что часть кода в вашем приложении может вызывать утечку, тогда можно сделать следующее:

  1. Скомпилировать и запустить отладочную версию сборки на эмуляторе или устройстве подключенному к вашему компьютеру;
  2. Перейти к подозрительной операции, затем вернуться к предыдущему действию, которое выведет подозрительную операцию из стека задач;
  3. В Android Studio открыть Android Monitor window → Memory section и нажать на кнопку запуска сборщика мусора (Initiate GC). Затем нажать кнопку Dump Java Heap ;
  4. После нажатия кнопки Dump Java Heap Android Studio откроет файл .hprof . Существует несколько способов проверки утечки памяти через этот файл. Вы можете использовать Analyzer Tasks в правом верхнем углу для автоматического обнаружения утечек. Или же можно переключиться в режим Tree View и найти действие, которое должно быть отключено. Проверяем данные Total Count , и если нашли отличия в данных, значит, что где-то есть утечка памяти.
  5. Как только была обнаружена утечка, нужно проверить дерево ссылок и узнать, какой объект ее вызывает.

Каковы общие схемы утечек?

Есть множество причин, по которым происходит утечка памяти в Android. Но все они могут быть отнесены к трем категориям.

  • утечки памяти, инициируемые статической ссылкой;
  • утечки памяти, инициируемые рабочим процессом;
  • просто утечка.

Можно загрузить приложение SinsOfMemoryLeaks, которое поможет определить, где происходит утечка.

В ветке Leak будут видны причины утечки памяти. Это приложение можно также запустить на устройстве или эмуляторе и использовать вышеупомянутые инструменты для отслеживания утечек. В ветке FIXED можно увидеть советы, как исправить утечки. После исправления процедуру можно повторить заново, чтобы окончательно убедиться в том, что утечки исправлены. Каждая из веток приложения имеет разные идентификаторы приложений, поэтому вы можете установить их на одном устройстве и проверять показания одновременно.

А теперь быстро пройдемся по всем видам утечек.

Утечки памяти, инициируемые статической ссылкой

Статическая ссылка сохраняется до тех пор, пока ваше приложение находится в памяти. У операций есть свои жизненные циклы, которые прекращаются и начинаются во время работы с приложением. Если вы обращаетесь к операции прямо или косвенно со статической ссылки, сборщик мусора не очистит занимаемую память после завершения операции. Память, занимаемая определенной операцией, может варьировать от нескольких килобайт до нескольких мегабайт в зависимости от того, в каком состоянии находится приложение. Если у него большая иерархия представлений или изображения с высоким разрешением, это может привести к утечке большого количества памяти.

Читайте также:  Усадочные трубки для проводов

Некоторые особенности утечек для этой категории:

Утечки памяти, инициируемые рабочим процессом

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

Тот же принцип применяется к таким потокам, как thread pool или ExecutorService .

Просто утечка

Каждый раз при запуске рабочего потока из операции вы сами отвечаете за управление потоком. Поскольку рабочий поток может работать дольше самой операции, нужно остановить его, когда действие будет прекращено. Если этого не сделать, существует вероятность утечки памяти рабочего процесса. Как в этом репозитории.

Каково влияние конкретной утечки?

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

Насколько велика утечка памяти?

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

Как долго длится утечка?

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

Сколько объектов в утечке?

В некоторых случаях утечку порождает только один объект, например, один из примеров статических ссылок, показанный в приложении SinsOfMemoryLeaks. Как только будет создано новое действие, оно начнет ссылаться на новую операцию. Старая утечка будет очищена сборщиком мусора. Таким образом, максимальная утечка всегда равна размеру одного экземпляра операции. Однако другие утечки продолжают просачиваться в новые объекты по мере их создания. В примере Leaking Threads активность пропускает по одному потоку каждый раз при его создании. Поэтому, если вы поворачиваете устройство 20 раз, утечка составит 20 рабочих потоков. Это закончится весьма печально, так как приложение заполнит всю доступную память на устройстве.

Как исправить и предотвратить утечки

Посмотрите как происходит устранение типичных утечек памяти в этой ветке репозитория. Решения можно обобщить до следующих пунктов:

  1. Нужно быть очень осторожными, принимая решение установки статической переменной для рабочего процесса. Это действительно необходимо? Возможно, эта переменная ссылается на процесс напрямую или косвенно (ссылка на объект внутреннего класса, прикрепленный экран и т. д.)? Если да, возможно ли будет очистить отсылку к процессу, используя функцию onDestroy ?
  2. Если было решено передавать операцию как синглтон или x-manager , нужно понимать, что делает другой объект с экземпляром действия. Нужно очистить ссылку (установить в null), если необходимо, используя для этого процесса функцию onDestroy .
  3. При создании класса внутри процесса, по возможности старайтесь сделать его статическим. Внутренние классы и анонимные классы имеют неявную ссылку на родительский класс. Поэтому, если экземпляр внутреннего/анонимного класса живет дольше, чем родительский класс, могут возникнуть проблемы. Например, при создании анонимного класса runnable и передаче его в рабочий поток или класс анонимного обработчика и использования его для передачи задач в другой поток существует риск утечки содержащегося объекта класса. Чтобы избежать риска утечки, нужно использовать статический класс, а не внутренний/анонимный класс.
  4. Если писать синглтон или x-manager класс, нужно сохранить ссылку на экземпляр слушателя (англ. «listener»). При этом вы не контролируете, что происходит со ссылкой (удалил ее пользователь класса или нет). В этом случае можно использовать WeakReference для создания ссылки на экземпляр слушателя. WeakReference не мешает сборщику мусора производить свои действия. Хотя эта функция отлично подходит для предотвращения утечек памяти, она также может вызвать побочный эффект, потому что нет гарантии, что ссылочный объект является активным, когда это необходимо. Поэтому рекомендуется использовать его в качестве последнего средства для исправления утечек памяти.
  5. Всегда нужно завершать рабочие потоки, инициированные функцией onDestroy() .

Не забудьте проверить примеры кода для типичных утечек памяти и способы их избежания в репозитории на Github.

Наши долгие поиски неустаревающих бестселлеров по оптимизации кода пока дают лишь первые результаты, но мы готовы вас порадовать, что буквально только что закончен перевод легендарной книги Бена Уотсона "Writing High Performance .NET Code". В магазинах — ориентировочно в апреле, следите за рекламой.

А сегодня предлагаем вам почитать сугубо практическую статью о наиболее насущных видах утечек оперативной памяти, которую написал Нельсон Ильхейдж (Nelson Elhage) из компании Stripe.

Итак, у вас получилась программа, на выполнение которой тратится чем дальше — тем больше времени. Вероятно, вам не составит труда понять, что это верный признак утечки в памяти.
Однако, что именно мы понимаем под «утечкой в памяти»? По моему опыту, явные утечки в памяти делятся на три основные категории, для каждой из которых характерно особое поведение, а для отладки каждой из категорий нужны особые инструменты и приемы. В этой статье я хочу описать все три класса и подсказать, каким образом правильно распознать, с
которым из классов вы имеете дело, и как найти утечку.

Тип (1): выделен недостижимый фрагмент памяти

Это классическая утечка памяти в C/C++. Кто-то выделил память при помощи new или malloc , и так и не вызвал free или delete , чтобы высвободить память по окончании работы с ней.

Как определить, что утечка относится именно к этой категории

  • Если вы пишете на C или C++, особенно на C++ без повсеместного использования умных указателей для управления сроками жизни сегментов памяти, то именно этот вариант рассматриваем в первую очередь.
  • Если программа выполняется в среде со сборкой мусора, то возможно, что утечка такого типа спровоцирована нативным расширением кода, однако, сначала нужно исключить утечки типов (2) и (3).

Как найти такую утечку

  • Пользуйтесь ASAN. Пользуйтесь ASAN. Пользуйтесь ASAN.
  • Пользуйтесь другим детектором. Я пробовал Valgrind или инструменты tcmalloc для работы с кучей, также есть и другие инструменты в других средах.
  • Некоторые распределители памяти позволяют дампировать профиль кучи, в котором будут показаны все невысвобожденные участки памяти. Если у вас утечка, то, спустя некоторое время, практически все активные выделения будут проистекать именно из нее, так что найти ее, вероятно, не составит труда.
  • Если ничего не помогает, выведите дамп памяти и изучите его максимально дотошно. Но начинать с этого определенно не следует.

Тип (2): незапланированно долгоживущие выделения памяти

Такие ситуации не являются “утечками” в классическом смысле слова, так как ссылка откуда-нибудь на этот участок памяти все-таки сохраняется, поэтому в конце концов он может быть высвобожден (если программа успеет туда добраться, не израсходовав всю память).
Ситуации из этой категории могут возникать по многим специфическим причинам. Наиболее распространенные таковы:

  • Непреднамеренное накапливание состояния в глобальной структуре; напр., HTTP-сервер записывает в глобальный список каждый получаемый объект Request .
  • Кэши без продуманной политики устаревания. Например, ORM-кэш, кэширующий все до единого загруженные объекты, активный в ходе миграции, при которой загружаются все без исключения записи, присутствующие в таблице.
  • Слишком объемное состояние захватывается в замыкании. Такой случай особенно распространен в JavаScript, но может встречаться и в других средах.
  • В более широком смысле, непреднамеренное удержание каждого из элементов массива или потока, тогда как предполагалось, что эти элементы будут обрабатываться в онлайновом потоковом режиме.
Читайте также:  Что означает количество ядер в процессоре

Как определить, что утечка относится именно к этой категории

  • Если программа выполняется в среде со сборкой мусора, то именно этот вариант рассматриваем в первую очередь.
  • Сравните размер кучи, выводимый в статистике сборщика мусора, с размером свободной памяти, выдаваемым операционной системой. Если утечка относится к этой категории, то цифры будут сравнимы и, что особенно важно, с течением времени будут следовать друг за другом.

Как найти такую утечку

Пользуйтесь профилировщиками или инструментами для дампа кучи, которые имеются в вашей среде. Я знаю, есть guppy в Python или memory_profiler в Ruby, а еще я сам написал ObjectSpace прямо на Ruby.

Тип (3): свободная, но неиспользуемая или непригодная для использования память

Охарактеризовать эту категорию сложнее всего, но именно ее наиболее важно понимать и учитывать.

Утечки такого типа возникают в серой зоне, между памятью, которая считается «свободной» с точки зрения распределителя внутри VM или среды времени выполнения, и памятью, которая «свободна» с точки зрения операционной системы. Наиболее распространенная (но не единственная) причина такого явления – фрагментация кучи. Некоторые распределители попросту берут и не возвращают память в операционную систему после того как та была выделена.

Случай такого рода можно рассмотреть на примере короткой программы, написанной на Python:

Мы выделяем 200 000 1-кб буферов, а затем сохраняем каждый последующий. Мы каждую секунду выводим состояние памяти с точки зрения операционной системы и с точки зрения собственного сборщика мусора Python.

У меня на ноутбуке получается примерно такой вывод:

start rss=232222720 gcsize=11667592
end rss=232222720 gcsize=5769520

Мы можем убедиться, что Python на самом деле высвободил половину буферов, ведь уровень gcsize упал практически наполовину от пикового значения, но не смог вернуть операционной системе ни байта этой памяти. Освобожденная память остается доступна все тому же процессу Python, но ни одному другому процессу на этой машине.

Такие свободные, но неиспользуемые фрагменты памяти могут быть как проблемными, так и безобидными. Если программа на Python так действует, а затем выделяет еще горсть 1kb-фрагментов, то данное пространство просто переиспользуется, и все хорошо.

Но, если бы мы делали это в ходе начальной настройки, а в дальнейшем выделяли память по минимуму, либо если бы все выделяемые впоследствии фрагменты были бы по 1,5kb и не вмещались в эти заблаговременно оставленные буферы, то вся выделенная таким образом память вечно простаивала бы впустую.

Проблемы такого рода особенно актуальны в специфической среде, а именно, в многопроцессных серверных системах для работы с такими языками как Ruby или Python.

Допустим, мы настроили систему, в которой:

  • На каждом сервере используется N однопоточных работников, конкурентно обслуживающих запросы. Давайте возьмем N=10 для точности.
  • Как правило, каждый работник располагает практически постоянным объемом памяти. Для точности давайте возьмем 500MB.
  • С некоторой невысокой частотой нам поступают запросы, требующие гораздо больше памяти, чем медианный запрос. Для точности, давайте предположим, что раз в минуту мы получаем запрос, на время исполнения которого дополнительно требуется лишний 1GB памяти, а по завершении обработки запроса эта память высвобождается.

Раз в минуту прибывает такой «китообразный» запрос, обработку которого мы поручаем одному из 10 работников, допустим, случайным образом:

random . В идеале, на время обработки этого запроса данный работник должен выделить 1GB оперативной памяти, а после окончания работы вернуть эту память операционной системе, чтобы в дальнейшем ее можно было снова использовать. Чтобы неограниченно долго обрабатывать запросы по такому принципу, серверу потребуется всего 10 * 500MB + 1GB = 6GB RAM.

Однако, давайте предположим, что из-за фрагментации или по какой-то другой причине, виртуальная машина никогда больше не сможет вернуть эту память операционной системе. То есть, объем оперативной памяти, который она требует от ОС, равен крупнейшему объему памяти, который когда-либо приходится выделять единовременно. В таком случае, когда конкретный работник обслуживает такой ресурсозатратный запрос, участок, занимаемый таким процессом в памяти, навсегда разбухает на целый гигабайт.

При запуске сервера вы увидите, что объем используемой памяти равен 10 * 500MB = 5GB. Как только поступит первый большой запрос, первый работник заграбастает 1GB памяти, а потом не отдаст ее обратно. Общий объем используемой памяти подскочит до 6GB. Следующие поступающие запросы могут время от времени перепадать тому процессу, который ранее уже обрабатывал «кита», и в таком случае объем используемой памяти не изменится. Но иногда такой крупный запрос будет доставаться уже другому работнику, из-за чего память будет раздуваться еще на 1GB, и так до тех пор, пока каждому работнику не доведется обработать такой крупный запрос как минимум однократно. В таком случае вы займете этими операциями до 10 * (500MB + 1GB) = 15GB оперативной памяти, что гораздо больше идеальных 6GB! Более того, если рассмотреть, как парк серверов используется с течением времени, то можно заметить, как объем используемой памяти постепенно вырастает с 5GB до 15GB, что будет очень напоминать «реальную» утечку.

Как определить, что утечка относится именно к этой категории

  • Сравните размер кучи, выводимый в статистике сборщика мусора, с размером свободной памяти, выдаваемым операционной системой. Если утечка относится к этой (третьей) категории, то цифры будут со временем расходиться.
  • Мне нравится настраивать мои сервера приложений так, чтобы оба этих числа периодически отбивались в моей инфраструктуре временных рядов, так по ним удобно выводить графики.
  • В Linux просмотрите состояние операционной системы в поле 24 из /proc/self/stat , а распределитель памяти просматривайте через API, специфичный для языка или виртуальной машины.

Как найти такую утечку

Как уже упоминалось, эта категория немного коварнее предыдущих, поскольку проблема зачастую возникает, даже когда все компоненты работают «как задумано». Тем не менее, есть ряд полезных приемов, помогающих смягчить или сократить воздействие таких «виртуальных утечек»:

  • Чаще перезапускайте ваши процессы. Если проблема нарастает медленно, то, возможно, перезапуск всех процессов приложения раз в 15 минут или раз в час может не составить никакого труда.
  • Еще более радикальный подход: можно научить все процессы перезапускаться самостоятельно, как только занимаемое ими пространство в памяти превышает некое пороговое значение или вырастает на заданную величину. Однако, постарайтесь предусмотреть, чтобы весь ваш парк серверов не мог пуститься в спонтанный синхронный перезапуск.
  • Поменяйте распределитель памяти. В долгосрочной перспективе tcmalloc и jemalloc обычно справляются с фрагментацией гораздо лучше, чем распределитель, задаваемый по умолчанию, а экспериментировать с ними очень удобно при помощи переменной LD_PRELOAD .
  • Выясните, есть ли у вас отдельные запросы, потребляющие гораздо больше памяти, чем остальные. У нас в Stripe серверы API измеряют RSS (постоянное потребление памяти) до и после обслуживания каждого запроса к API и логируют дельту. Затем мы без труда запрашиваем наши системы агрегации логов, чтобы определить, есть ли такие терминалы и пользователи (и прослеживаются ли закономерности), на которых можно списать всплески потребления памяти.
  • Отрегулируйте сборщик мусора/распределитель памяти. Во многих из них предусмотрены настраиваемые параметры, позволяющие задавать, насколько активно такой механизм будет возвращать память в операционную систему, насколько он оптимизирован на устранение фрагментации; там есть и другие полезные параметры. Здесь все также довольно сложно: убедитесь, что точно понимаете, что именно вы измеряете и оптимизируете, а также постарайтесь найти эксперта по соответствующей виртуальной машине и проконсультироваться с ним.

Вы можете помочь и перевести немного средств на развитие сайта

Комментировать
0 просмотров
Комментариев нет, будьте первым кто его оставит

Это интересно
Adblock detector