0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Утечки памяти в Go способны принимать разные формы. Как правило, мы считаем, что это баги, однако истинная причина проблем может быть заложена ещё на стадии проектирования. Рассмотрим распространённые примеры появления проблем с памятью: • неверное представление данных; • интенсивное применение рефлексии либо строк; • применение глобальных переменных; • бесконечные горутины.</p>
1
<p>Утечки памяти в Go способны принимать разные формы. Как правило, мы считаем, что это баги, однако истинная причина проблем может быть заложена ещё на стадии проектирования. Рассмотрим распространённые примеры появления проблем с памятью: • неверное представление данных; • интенсивное применение рефлексии либо строк; • применение глобальных переменных; • бесконечные горутины.</p>
2
<p>Помочь найти проблемы с памятью в Golang может инструмент под названием<strong>pprof</strong>. Также он позволяет находить проблемы в работе процессора.</p>
2
<p>Помочь найти проблемы с памятью в Golang может инструмент под названием<strong>pprof</strong>. Также он позволяет находить проблемы в работе процессора.</p>
3
<p>Инструмент pprof создаёт файл дампа, куда кладёт сэмпл кучи. Данный файл вы сможете в итоге проанализировать/визуализировать, чтобы позволит получить карту: • текущего выделения памяти; • накопительного (общего) выделения памяти.</p>
3
<p>Инструмент pprof создаёт файл дампа, куда кладёт сэмпл кучи. Данный файл вы сможете в итоге проанализировать/визуализировать, чтобы позволит получить карту: • текущего выделения памяти; • накопительного (общего) выделения памяти.</p>
4
<p>Кроме того, у pprof есть возможность сравнивать снимки, которые сделаны в разное время. Это бывает полезно при определении проблемных областей кода при стрессовых сценариях.</p>
4
<p>Кроме того, у pprof есть возможность сравнивать снимки, которые сделаны в разное время. Это бывает полезно при определении проблемных областей кода при стрессовых сценариях.</p>
5
<h2>Профили pprof</h2>
5
<h2>Профили pprof</h2>
6
<p>Инструмент pprof функционирует с помощью профилей. Под профилем понимается набор трассировок стека, которые показывают последовательности вызовов, ставших причиной появления определённого события, к примеру, выделения памяти. Подробная информация о реализации профилей содержится в файле<a>runtime/pprof/pprof.go</a>.</p>
6
<p>Инструмент pprof функционирует с помощью профилей. Под профилем понимается набор трассировок стека, которые показывают последовательности вызовов, ставших причиной появления определённого события, к примеру, выделения памяти. Подробная информация о реализации профилей содержится в файле<a>runtime/pprof/pprof.go</a>.</p>
7
<p>Язык программирования Go имеет целый перечень встроенных профилей, которые вы можете применять в стандартных ситуациях: • goroutine - следы всех текущих горутин; • allocs - выборка всех предыдущих выделений памяти; • heap - выборка выделений памяти живых объектов; • threadcreate - следы стека, ставшие причиной создания новых потоков в ОС; • mutex - следы стека держателей конфликтующих<a>мьютексов</a>; • block - следы стека, ставшие причиной блокировки<a>примитивов синхронизации</a>.</p>
7
<p>Язык программирования Go имеет целый перечень встроенных профилей, которые вы можете применять в стандартных ситуациях: • goroutine - следы всех текущих горутин; • allocs - выборка всех предыдущих выделений памяти; • heap - выборка выделений памяти живых объектов; • threadcreate - следы стека, ставшие причиной создания новых потоков в ОС; • mutex - следы стека держателей конфликтующих<a>мьютексов</a>; • block - следы стека, ставшие причиной блокировки<a>примитивов синхронизации</a>.</p>
8
<h2>Heap</h2>
8
<h2>Heap</h2>
9
<p>Heap (куча) представляет собой абстрактное представление места, в котором ОС хранит объекты, использующие код. В дальнейшем эта память очищается сборщиком мусора либо освобождается вручную.</p>
9
<p>Heap (куча) представляет собой абстрактное представление места, в котором ОС хранит объекты, использующие код. В дальнейшем эта память очищается сборщиком мусора либо освобождается вручную.</p>
10
<p>Однако куча не является единственным местом, где осуществляется выделение памяти - часть памяти выделяется и на стеке. В языке программирования Go стек обычно используют для присвоений, которые происходят в рамках работы функции. Также в Go используется стек в случае, когда компилятор "знает", сколько конкретно памяти надо зарезервировать перед выполнением (к примеру, для массивов фиксированного размера).</p>
10
<p>Однако куча не является единственным местом, где осуществляется выделение памяти - часть памяти выделяется и на стеке. В языке программирования Go стек обычно используют для присвоений, которые происходят в рамках работы функции. Также в Go используется стек в случае, когда компилятор "знает", сколько конкретно памяти надо зарезервировать перед выполнением (к примеру, для массивов фиксированного размера).</p>
11
<p>При этом данные кучи должны быть освобождены с применением сборки мусора, а вот данные стека - нет. Именно поэтому гораздо эффективнее применять стек там, где это представляется возможным.</p>
11
<p>При этом данные кучи должны быть освобождены с применением сборки мусора, а вот данные стека - нет. Именно поэтому гораздо эффективнее применять стек там, где это представляется возможным.</p>
12
<h2>Получаем данные кучи посредством pprof</h2>
12
<h2>Получаем данные кучи посредством pprof</h2>
13
<p>Существуют 2 основных способа получить данные. Первый, как правило, применяют в качестве части теста - он включает импорт runtime/pprof с последующим вызовом pprof.WriteHeapProfile(some_file) в целях записи информации в кучу.</p>
13
<p>Существуют 2 основных способа получить данные. Первый, как правило, применяют в качестве части теста - он включает импорт runtime/pprof с последующим вызовом pprof.WriteHeapProfile(some_file) в целях записи информации в кучу.</p>
14
// Функция lookup() берёт профиль namepprof.Lookup("heap").WriteTo(some_file, 0)<p>Здесь WriteHeapProfile() существует для обратной совместимости. Однако прочие профили таких возможностей не имеют, поэтому для получения данных профилей вам следует применять функцию Lookup().</p>
14
// Функция lookup() берёт профиль namepprof.Lookup("heap").WriteTo(some_file, 0)<p>Здесь WriteHeapProfile() существует для обратной совместимости. Однако прочие профили таких возможностей не имеют, поэтому для получения данных профилей вам следует применять функцию Lookup().</p>
15
<p>Другой способ - пустить его через HTTP (по web-адресу). Этот способ позволяет извлекать определённые данные из запущенного контейнера в тестовой среде либо e2e-среде или даже из production. При этом<a>всю документацию пакета</a>pprof вы можете и не читать, но как его включить, вы знать должны.</p>
15
<p>Другой способ - пустить его через HTTP (по web-адресу). Этот способ позволяет извлекать определённые данные из запущенного контейнера в тестовой среде либо e2e-среде или даже из production. При этом<a>всю документацию пакета</a>pprof вы можете и не читать, но как его включить, вы знать должны.</p>
16
import ( "net/http" _ "net/http/pprof" ) ... func main() { ... http.ListenAndServe("localhost:8080", nil) }<p>"Побочный эффект" импорта net/http/pprof - регистрация конечных адресов на web-сервере в корневом каталоге /debug/pprof. Применяя curl, мы сможем получить файлы с нужной информацией для анализа.</p>
16
import ( "net/http" _ "net/http/pprof" ) ... func main() { ... http.ListenAndServe("localhost:8080", nil) }<p>"Побочный эффект" импорта net/http/pprof - регистрация конечных адресов на web-сервере в корневом каталоге /debug/pprof. Применяя curl, мы сможем получить файлы с нужной информацией для анализа.</p>
17
$ curl -sK -v http://localhost:8080/debug/pprof/heap > heap.out<p>В примере выше добавление http.ListenAndServe() потребуется лишь в том случае, когда программа раньше не имела прослушивателя HTTP-сервера. Также есть способы настроить его посредством ServeMux.HandleFunc().</p>
17
$ curl -sK -v http://localhost:8080/debug/pprof/heap > heap.out<p>В примере выше добавление http.ListenAndServe() потребуется лишь в том случае, когда программа раньше не имела прослушивателя HTTP-сервера. Также есть способы настроить его посредством ServeMux.HandleFunc().</p>
18
<h2>Используем pprof на практике</h2>
18
<h2>Используем pprof на практике</h2>
19
<p>Существуют 2 главные стратегии анализа памяти посредством pprof. Первая - это<strong>inuse</strong>. Она подразумевает рассмотрение текущих выделений памяти (байтов либо числа объектов). Вторую называют<strong>alloc</strong>. Она предполагает просматривание всех выделенных байтов либо количества объектов в процессе выполнения программы.</p>
19
<p>Существуют 2 главные стратегии анализа памяти посредством pprof. Первая - это<strong>inuse</strong>. Она подразумевает рассмотрение текущих выделений памяти (байтов либо числа объектов). Вторую называют<strong>alloc</strong>. Она предполагает просматривание всех выделенных байтов либо количества объектов в процессе выполнения программы.</p>
20
<p>Выборкой выделения памяти является профиль heap. При этом "за кулисами" pprof применяет функцию runtime.MemProfile(), которая собирает информацию о предоставлении памяти на каждые 512 КБ выделенных байтов, делая это по умолчанию. Мы можем изменить MemProfile() в целях сбора информации о всех объектах, но такой шаг, вероятнее всего, замедлит работу нашего приложения.</p>
20
<p>Выборкой выделения памяти является профиль heap. При этом "за кулисами" pprof применяет функцию runtime.MemProfile(), которая собирает информацию о предоставлении памяти на каждые 512 КБ выделенных байтов, делая это по умолчанию. Мы можем изменить MemProfile() в целях сбора информации о всех объектах, но такой шаг, вероятнее всего, замедлит работу нашего приложения.</p>
21
<p>Итак, как только файл профиля будет собран, его можно загружать в интерактивную консоль pprof.</p>
21
<p>Итак, как только файл профиля будет собран, его можно загружать в интерактивную консоль pprof.</p>
22
<p>Давайте посмотрим на отображаемую информацию:</p>
22
<p>Давайте посмотрим на отображаемую информацию:</p>
23
Type: inuse_space Time: Jan 22, 2019 at 1:08pm (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof)<p>Обратите внимание на Type: inuse_space. Дело в том, что мы смотрим на данные выделения памяти в конкретный момент, то есть когда захватили профиль. Тип - это значение конфигурации sample_index, а возможными значениями бывают: • inuse_space - объём выделенной и пока не освобождённой памяти; • inuse_object s - число выделенных и пока не освобождённых объектов; • alloc_space - общий объём выделенной памяти (вне зависимости от освобождённой); • alloc_objects - общее число выделенных объектов (вне зависимости от освобождённых).</p>
23
Type: inuse_space Time: Jan 22, 2019 at 1:08pm (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof)<p>Обратите внимание на Type: inuse_space. Дело в том, что мы смотрим на данные выделения памяти в конкретный момент, то есть когда захватили профиль. Тип - это значение конфигурации sample_index, а возможными значениями бывают: • inuse_space - объём выделенной и пока не освобождённой памяти; • inuse_object s - число выделенных и пока не освобождённых объектов; • alloc_space - общий объём выделенной памяти (вне зависимости от освобождённой); • alloc_objects - общее число выделенных объектов (вне зависимости от освобождённых).</p>
24
<p>Если вы теперь введёте в интерактивную консоль top, в выводе отобразятся главные потребители памяти.</p>
24
<p>Если вы теперь введёте в интерактивную консоль top, в выводе отобразятся главные потребители памяти.</p>
25
<p>Например, можно заметить строку, показывающую сброшенные узлы (Dropped Nodes). Узел представляет собой выделение объекта либо "узел" в дереве. Удалять узлы в целях уменьшения количества мусора - вроде бы неплохая идея, однако порой это может скрывать первопричину утечек памяти.</p>
25
<p>Например, можно заметить строку, показывающую сброшенные узлы (Dropped Nodes). Узел представляет собой выделение объекта либо "узел" в дереве. Удалять узлы в целях уменьшения количества мусора - вроде бы неплохая идея, однако порой это может скрывать первопричину утечек памяти.</p>
26
<p>Если пожелаете включить все данные профиля, следует при запуске pprof добавить опцию<em>-nodefraction=0</em>либо ввести в интерактивной консоли<em>nodefraction=0</em>.</p>
26
<p>Если пожелаете включить все данные профиля, следует при запуске pprof добавить опцию<em>-nodefraction=0</em>либо ввести в интерактивной консоли<em>nodefraction=0</em>.</p>
27
<p>В выводимом списке вы сможете увидеть 2 значения - flat и cum. Что они означают: • flat - память выделена функцией и ей же удерживается; • cum - память выделена функцией либо функцией, вызванной стеком.</p>
27
<p>В выводимом списке вы сможете увидеть 2 значения - flat и cum. Что они означают: • flat - память выделена функцией и ей же удерживается; • cum - память выделена функцией либо функцией, вызванной стеком.</p>
28
<p>В целом, этой информации обычно хватает, чтобы понять, существует ли проблема. К примеру, функция отвечает за выделение большого объёма памяти, но при этом она не удерживает её. Такое положение вещей будет значить, что какой-либо иной объект указывает на данную память и удерживает её - следовательно, возможно возникновение утечки памяти либо бага.</p>
28
<p>В целом, этой информации обычно хватает, чтобы понять, существует ли проблема. К примеру, функция отвечает за выделение большого объёма памяти, но при этом она не удерживает её. Такое положение вещей будет значить, что какой-либо иной объект указывает на данную память и удерживает её - следовательно, возможно возникновение утечки памяти либо бага.</p>
29
<p>Отдельно стоит сказать про команду<strong>top</strong>в интерактивной консоли. По умолчанию она выведет первые десять позиций потребителей памяти. Однако данная команда поддерживает формат topN, причём N здесь - это число записей, которые вы желаете увидеть. К примеру, при наборе top70 выведутся все узлы.</p>
29
<p>Отдельно стоит сказать про команду<strong>top</strong>в интерактивной консоли. По умолчанию она выведет первые десять позиций потребителей памяти. Однако данная команда поддерживает формат topN, причём N здесь - это число записей, которые вы желаете увидеть. К примеру, при наборе top70 выведутся все узлы.</p>
30
<p><em>По материалам статьи "<a>How I investigated memory leaks in Go using pprof on a large codebase</a>"</em></p>
30
<p><em>По материалам статьи "<a>How I investigated memory leaks in Go using pprof on a large codebase</a>"</em></p>
31
31