[{"data":1,"prerenderedAt":1595},["ShallowReactive",2],{"content-\u002F04-advanced\u002F04-gc":3},{"id":4,"title":5,"body":6,"description":244,"difficulty":1580,"extension":1581,"meta":1582,"module":1580,"navigation":1501,"next":1583,"order":1311,"path":1584,"prev":1585,"seo":1586,"slug":1587,"stem":1588,"tags":1589,"__hash__":1594},"content\u002F04-advanced\u002F04-gc\u002Findex.md","Сборщик мусора (GC)",{"type":7,"value":8,"toc":1553},"minimark",[9,22,25,30,48,54,58,65,69,76,82,88,95,102,106,112,116,122,125,127,145,147,151,154,158,165,168,171,177,181,187,193,195,226,228,232,235,245,249,252,256,262,268,272,277,283,298,302,305,307,354,356,359,365,371,375,381,387,394,398,402,406,410,413,417,423,429,431,492,497,503,507,510,515,518,521,524,530,535,538,543,549,558,564,566,603,605,610,616,621,627,638,644,649,655,663,669,674,680,683,686,691,694,697,703,706,711,714,720,723,728,731,734,736,764,766,769,773,779,782,788,794,798,851,858,862,866,872,875,881,887,892,960,964,970,973,976,978,1013,1015,1018,1024,1029,1035,1041,1044,1050,1056,1058,1079,1081,1084,1090,1096,1099,1104,1107,1110,1113,1122,1124,1143,1145,1148,1151,1157,1161,1167,1170,1174,1177,1183,1190,1192,1232,1234,1237,1242,1315,1320,1326,1331,1348,1357,1363,1369,1374,1460,1465,1471,1476,1547,1549],[10,11,12,16,19],"ul",{},[13,14,15],"li",{},"4 фазы: Sweep Termination (STW) → Mark (concurrent) → Mark Termination (STW) → Sweep (concurrent)",[13,17,18],{},"STW паузы: ~10-30µs + ~60-90µs, основная работа concurrent",[13,20,21],{},"триггер: heap goal, 2 мин без GC, runtime.GC()",[23,24],"hr",{},[26,27,29],"h2",{"id":28},"триггер-запуска","Триггер запуска",[31,32,33,34,38,39,43,44,47],"p",{},"Heap вырос до ",[35,36,37],"code",{},"Live heap + (Live heap + stacks + globals) × GOGC\u002F100"," — момент запуска выбирает ",[40,41,42],"strong",{},"GC Pacer",", параметры задаются через ",[40,45,46],{},"GOGC и GOMEMLIMIT",".",[31,49,50,51,47],{},"Также запускается если прошло >2 минут без GC (sysmon) или вызван ",[35,52,53],{},"runtime.GC()",[26,55,57],{"id":56},"фаза-1-sweep-termination-stw-10-30µs","Фаза 1: Sweep Termination (STW, ~10-30µs)",[31,59,60,61,64],{},"Останавливаем все горутины. Дочищаем sweep предыдущего цикла (если не закончили). Включаем ",[40,62,63],{},"Write barrier",". Возобновляем горутины.",[26,66,68],{"id":67},"фаза-2-mark-concurrent-25-cpu","Фаза 2: Mark (concurrent, ~25% CPU)",[31,70,71,72,75],{},"Работает ",[40,73,74],{},"одновременно с программой",", несколькими потоками параллельно.",[31,77,78,79,47],{},"Объекты не помечены (white по умолчанию). От root objects (стеки горутин + глобальные + runtime) обходим граф, помечаем живые — это ",[40,80,81],{},"Tri-color marking",[31,83,84,85,87],{},"Программа продолжает работать и меняет указатели — ",[40,86,63],{}," следит, чтобы GC не потерял живой объект.",[31,89,90,91,94],{},"Если горутина аллоцирует быстрее, чем GC маркирует — ",[40,92,93],{},"Mark Assist"," заставляет её помогать GC.",[31,96,97,98,101],{},"Объекты с ",[40,99,100],{},"Finalizers"," не удаляются — ставятся в очередь, будут освобождены в следующем цикле.",[26,103,105],{"id":104},"фаза-3-mark-termination-stw-60-90µs","Фаза 3: Mark Termination (STW, ~60-90µs)",[31,107,108,109,111],{},"Останавливаем горутины. Дочищаем очередь серых объектов. Убеждаемся что серых нет. Выключаем ",[40,110,63],{},". Собираем статистику для следующего цикла. Возобновляем горутины.",[26,113,115],{"id":114},"фаза-4-sweep-concurrent-lazy","Фаза 4: Sweep (concurrent + lazy)",[31,117,118,119,47],{},"Работает в фоне параллельно с программой. Освобождает white объекты — память возвращается ",[40,120,121],{},"Пример аллокации",[31,123,124],{},"Часть sweep происходит прямо при новых аллокациях (lazy). Scavenger возвращает совсем ненужное ОС.",[23,126],{},[10,128,129,136,142],{},[13,130,131,132,135],{},"мусор = память, которая ",[40,133,134],{},"раньше использовалась",", но стала никому не нужна",[13,137,138,139],{},"GC = всегда overhead (ручной или автоматический). Иногда мусор можно ",[40,140,141],{},"не собирать",[13,143,144],{},"исключения: короткоживущие воркеры, биржевые приложения с рестартом между сессиями",[23,146],{},[26,148,150],{"id":149},"что-такое-мусор","Что такое мусор",[31,152,153],{},"Память, которая раньше была нужна, но стала неиспользуемой. Например: вызвали функцию, аллоцировали гигабайт, вышли — эта память никому не нужна. Её нужно освободить, чтобы аллокатор мог переиспользовать это место.",[26,155,157],{"id":156},"всегда-ли-нужно-собирать-мусор","Всегда ли нужно собирать мусор?",[31,159,160,161,164],{},"Нет. GC — это ",[40,162,163],{},"всегда overhead",": дополнительные операции, CPU, паузы.",[31,166,167],{},"Иногда можно не собирать:",[31,169,170],{},"Воркер, который отработает несколько минут — памяти хватает, зачем тратить CPU на GC? Биржевое приложение (10:00–20:00) — если памяти хватает на весь день, можно рестартнуть перед следующей сессией.",[31,172,173,176],{},[40,174,175],{},"Не значит, что так нужно делать."," Это исключительные ситуации. Но важно понимать: если GC не нужен — мы экономим CPU и избегаем пауз.",[26,178,180],{"id":179},"способы-сборки-мусора","Способы сборки мусора",[31,182,183,186],{},[40,184,185],{},"Ручной (C\u002FC++):"," программист сам malloc\u002Ffree. Минимальный overhead. Проблемы: double-free, забытый free, утечки при разрастании проекта.",[31,188,189,192],{},[40,190,191],{},"Автоматический:"," два основных подхода — reference counting и tracing. Больше overhead, но программист не думает об освобождении.",[23,194],{},[10,196,197,200,203,223],{},[13,198,199],{},"каждый объект хранит счётчик ссылок. Копируют ссылку → инкремент. Удаляют ссылку → декремент.  Счётчик = 0 → объект можно освободить",[13,201,202],{},"плюс: не нужен фоновый поток GC. Основной поток сам видит ref_count = 0 и освобождает.",[13,204,205,206],{},"минусы:\n",[10,207,208,211,214,217],{},[13,209,210],{},"overhead на ведение счетчика",[13,212,213],{},"синхронизация счетчиков между разными потоками,",[13,215,216],{},"циклические ссылки(двухсвязный список)",[13,218,219,222],{},[40,220,221],{},"Каскадное синхронное освобождение:"," связанный список на миллион элементов → удаляем head → поочерёдно освобождается каждый элемент → spike latency.",[13,224,225],{},"почему не подошло для Go: weak\u002Fstrong refs = дополнительная сложность для программиста",[23,227],{},[26,229,231],{"id":230},"идея","Идея",[31,233,234],{},"Каждый объект содержит счётчик: сколько других объектов на него ссылаются. Копируют ссылку → инкремент. Удаляют ссылку → декремент. Счётчик = 0 → объект можно освободить.",[236,237,242],"pre",{"className":238,"code":240,"language":241},[239],"language-text","Object в heap:\n  [данные | ref_count = 2]\n       ↑           ↑\n    obj_A        obj_B (копия)\n\nobj_B уничтожен → ref_count = 1\nobj_A уничтожен → ref_count = 0 → free!\n","text",[35,243,240],{"__ignoreMap":244},"",[26,246,248],{"id":247},"плюсы","Плюсы",[31,250,251],{},"Не нужен никакой фоновый поток для сборки мусора. Основной поток сам видит ref_count = 0 и освобождает.",[26,253,255],{"id":254},"минусы","Минусы",[31,257,258,261],{},[40,259,260],{},"Overhead:"," дополнительная память на счётчик + инструкции инкремента\u002Fдекремента при каждой операции.",[31,263,264,267],{},[40,265,266],{},"Синхронизация:"," из нескольких потоков нужны атомики или мьютексы для счётчика.",[31,269,270,222],{},[40,271,221],{},[31,273,274],{},[40,275,276],{},"Циклические ссылки (главная проблема):",[236,278,281],{"className":279,"code":280,"language":241},[239],"Двусвязный список:\n  A.next → B     ref_count(A) = 2\n  B.prev → A     ref_count(B) = 2\n\nУдалили внешний указатель:\n  ref_count(A) = 1 (B всё ещё ссылается)\n  ref_count(B) = 1 (A всё ещё ссылается)\n  → Никогда не станет 0 → утечка памяти!\n",[35,282,280],{"__ignoreMap":244},[31,284,285,286,289,290,293,294,297],{},"Решение в C++: ",[35,287,288],{},"weak_ptr"," \u002F ",[35,291,292],{},"shared_ptr"," (слабые\u002Fсильные ссылки, разные счётчики). Но это ",[40,295,296],{},"дополнительная сложность для программиста",": нужно знать, где weak, где strong.",[26,299,301],{"id":300},"почему-не-подошло-для-go","Почему не подошло для Go",[31,303,304],{},"Если бы Go использовал ref counting, программисту пришлось бы знать про weak\u002Fstrong refs, управлять ими. Дополнительные контейнеры, функции, вопросы на собесах — язык усложняется. Go выбрал tracing GC, где программист вообще не думает об освобождении.",[23,306],{},[10,308,309,316,336,342,348],{},[13,310,311,312,315],{},"tracing ищет ",[40,313,314],{},"живые"," объекты (ref counting ищет мёртвые) — семантически противоположные подходы, все что не живые - мертвые",[13,317,318,319],{},"STW: остановить мутаторы → обойти граф от root set → пометить живые → удалить непомеченные\n",[10,320,321,327],{},[13,322,323,326],{},[40,324,325],{},"мутаторы"," - потоки которые выполняют что-то, мутирую память",[13,328,329,332,333],{},[40,330,331],{},"root set"," - стеки всех горутин + глобальные переменные + runtime-структуры, место, ",[40,334,335],{},"откуда начинаются все цепочки ссылок на heap",[13,337,338,341],{},[40,339,340],{},"Проблема",": Чем больше куча → тем больше граф → тем дольше обход → тем дольше паузы.",[13,343,344,347],{},[40,345,346],{},"Sweep (Go):"," освободить белые (непомеченные) объекты. Быстро, но heap остаётся фрагментированным.",[13,349,350,353],{},[40,351,352],{},"Copying:"," перенести живые объекты в новое место компактно, старое освободить целиком. Дольше, но нет фрагментации.",[23,355],{},[26,357,231],{"id":358},"идея-1",[31,360,361,362,364],{},"В отличие от ref counting, tracing ищет ",[40,363,314],{}," объекты, а не мёртвые. Всё, до чего не дошли — мусор.",[31,366,367,370],{},[40,368,369],{},"Мутаторы"," — потоки, выполняющие бизнес-логику (называются так, потому что мутируют память: создают объекты, изменяют ссылки).",[26,372,374],{"id":373},"алгоритм-простейший-stw","Алгоритм (простейший, STW)",[236,376,379],{"className":377,"code":378,"language":241},[239],"1. Остановить все мутаторы (Stop the World)\n2. Обойти граф ссылок от root set (стеки + глобальные)\n   - можно параллельно, несколькими потоками\n3. Пометить все живые объекты\n4. Непомеченные = мусор → удалить\n",[35,380,378],{"__ignoreMap":244},[31,382,383,386],{},[40,384,385],{},"Root set:"," стеки всех горутин + глобальные переменные. Оттуда начинаются все цепочки ссылок в heap.",[31,388,389,390,393],{},"Фаза маркировки может выполняться ",[40,391,392],{},"параллельно несколькими потоками"," внутри STW.",[26,395,397],{"id":396},"фаза-очистки-sweep-vs-copying","Фаза очистки: Sweep vs Copying",[31,399,400,347],{},[40,401,346],{},[31,403,404,353],{},[40,405,352],{},[26,407,409],{"id":408},"проблема-stw","Проблема STW",[31,411,412],{},"Чем больше куча → тем больше граф → тем дольше обход → тем дольше паузы. Для больших приложений неприемлемо: циклические спайки latency на время каждой сборки.",[26,414,416],{"id":415},"как-уменьшить-паузы","Как уменьшить паузы",[31,418,419,422],{},[40,420,421],{},"Поколения:"," сканировать не всю кучу, а только молодые объекты (чаще умирают).",[31,424,425,428],{},[40,426,427],{},"Concurrent GC:"," не останавливать мутаторы, работать параллельно с ними. Go пошёл по пути concurrent (без поколений).",[23,430],{},[10,432,433,436,452,475],{},[13,434,435],{},"базовый алгоритм маркировки GC. В чистом виде требует STW",[13,437,438,439],{},"Go работает concurrent → нужны доп. механизмы:\n",[10,440,441,447],{},[13,442,443,446],{},[40,444,445],{},"Hybrid write barrier"," — корректность (не потерять живой объект)",[13,448,449,451],{},[40,450,93],{}," — скорость (GC успевает за аллокациями)",[13,453,454,455],{},"три цвета:\n",[10,456,457,463,469],{},[13,458,459,462],{},[40,460,461],{},"White"," = не помечен, кандидат на удаление",[13,464,465,468],{},[40,466,467],{},"Grey"," = обнаружен, но ссылки не просканированы",[13,470,471,474],{},[40,472,473],{},"Black"," = просканирован, все ссылки обработаны",[13,476,477,478],{},"инварианты (правила корректности для concurrent режима):\n",[10,479,480,486],{},[13,481,482,485],{},[40,483,484],{},"Strong"," (Go до 1.8): чёрный НЕ может указывать на белый",[13,487,488,491],{},[40,489,490],{},"Weak"," (Go 1.8+): чёрный МОЖЕТ указывать на белый, но белый должен быть достижим через серый",[493,494,496],"h4",{"id":495},"алгоритм","Алгоритм",[236,498,501],{"className":499,"code":500,"language":241},[239],"1. Объекты не помечены (white по умолчанию)\n2. Root objects → grey (стеки горутин + глобальные + runtime)\n3. Цикл пока есть серые:\n   - берём серый объект из очереди (назовём его ТЕКУЩИЙ)\n   - смотрим на кого ТЕКУЩИЙ ссылается (его дети)\n   - для каждого ребёнка:\n     - ребёнок white → красим ребёнка в grey, кладём ребёнка в очередь\n     - ребёнок grey\u002Fblack → пропускаем\n   - всех детей просмотрели → ТЕКУЩИЙ → black (полностью обработан)\n4. Конец: серых нет → всё что white = мусор → sweep\n",[35,502,500],{"__ignoreMap":244},[26,504,506],{"id":505},"основа","Основа",[31,508,509],{},"Алгоритм маркировки живых объектов. Три цвета = состояние объекта.",[31,511,512],{},[40,513,514],{},"Цвета:",[31,516,517],{},"White — не помечен, кандидат на удаление.",[31,519,520],{},"Grey — обнаружен, но ссылки ещё не просканированы (фронт волны обхода).",[31,522,523],{},"Black — просканирован, все ссылки обработаны.",[31,525,526,529],{},[40,527,528],{},"Зачем три цвета, а не два?"," Для concurrent маркировки. Grey означает «обнаружен, но дети ещё не просканированы». Без него невозможно понять, какой объект уже полностью обработан, а какой нет, пока мутаторы параллельно меняют указатели. Два цвета хватило бы только для STW-сборщика.",[31,531,532],{},[40,533,534],{},"Алгоритм:",[31,536,537],{},"Обход можно параллелить: каждый поток забирает элемент из очереди и идёт в глубину.",[31,539,540],{},[40,541,542],{},"Инварианты (гарантии корректности):",[31,544,545,548],{},[40,546,547],{},"Strong tri-color invariant",": чёрный объект НЕ может указывать на белый. Go использовал до 1.8 (Dijkstra barrier).",[31,550,551,554,555,557],{},[40,552,553],{},"Weak tri-color invariant",": чёрный МОЖЕТ указывать на белый, НО белый должен быть достижим через серый. Go 1.8+ использует это (hybrid ",[40,556,63],{},").",[31,559,560,563],{},[40,561,562],{},"Почему базовый tri-color marking требует STW?"," В чистом виде: если мутатор одновременно меняет указатели, он может создать ситуацию когда чёрный указывает на белый (invariant нарушен), и GC удалит живой объект. Concurrent режим требует write barrier для поддержания инвариантов.",[23,565],{},[10,567,568,574,577,580,586,593,600],{},[13,569,570,573],{},[40,571,572],{},"write barrier"," — общий принцип: при записи указателя → уведомить GC, чтобы не потерять живой объект",[13,575,576],{},"три реализации: insertion (новый ребёнок → серый), deletion (старый ребёнок → серый), hybrid (оба ребёнка → серый)",[13,578,579],{},"минусы: insertion — стек не покрыт → rescan стеков в Mark Termination (долгий STW). Deletion — мусор живёт +1 цикл GC",[13,581,582,585],{},[40,583,584],{},"Go использует hybrid write barrier (1.8+):"," стек без barrier (дорого), стеки сканируются целиком один раз. Rescan не нужен → паузы короче",[13,587,588,589,592],{},"включение\u002Fвыключение требует ",[40,590,591],{},"короткого STW"," (~10-30µs), активен только в Mark phase",[13,594,595,596,599],{},"overhead: ~2 инструкции на запись указателя. Fast path: проверка ",[35,597,598],{},"writeBarrier.enabled"," (почти бесплатно когда GC не активен). Общий overhead ~10-20% на pointer-heavy код",[13,601,602],{},"корнер-кейс: серые не заканчиваются → после лимита новые объекты сразу чёрные, мусор доживёт до следующего цикла",[23,604],{},[31,606,607],{},[40,608,609],{},"Что такое write barrier",[31,611,612,615],{},[40,613,614],{},"Общий принцип:"," при записи указателя в heap → уведомить GC. Нужен потому что в concurrent режиме программа меняет указатели пока GC маркирует. Без barrier GC может потерять живой объект (lost object).",[31,617,618],{},[40,619,620],{},"Insertion barrier (Dijkstra):",[236,622,625],{"className":623,"code":624,"language":241},[239],"A.field = C   \u002F\u002F записали новый указатель A → C\n              \u002F\u002F новый ребёнок C → красится серым\n",[35,626,624],{"__ignoreMap":244},[31,628,629,630,633,634,637],{},"Появился ",[40,631,632],{},"новый ребёнок"," → красим его ",[40,635,636],{},"серым",", чтобы GC не пропустил.",[31,639,640,643],{},[40,641,642],{},"Минус insertion barrier:"," стек не покрыт → в Mark Termination нужен полный rescan стеков (долгий STW).",[31,645,646],{},[40,647,648],{},"Deletion barrier (Yuasa):",[236,650,653],{"className":651,"code":652,"language":241},[239],"\u002F\u002F было: A.field = B (B — ребёнок A)\nA.field = C   \u002F\u002F перезаписали: B отвязан от A\n              \u002F\u002F старый ребёнок B → красится серым\n",[35,654,652],{"__ignoreMap":244},[31,656,657,633,660,662],{},[40,658,659],{},"Ребёнок потерял родителя",[40,661,636],{},", чтобы GC не потерял B и его потомков.",[31,664,665,668],{},[40,666,667],{},"Минус deletion barrier:"," мусор живёт +1 цикл GC (старый ребёнок серый → не удалится в этом цикле).",[31,670,671],{},[40,672,673],{},"Hybrid (Go 1.8+):",[236,675,678],{"className":676,"code":677,"language":241},[239],"\u002F\u002F было: A.field = B\nA.field = C   \u002F\u002F и старый ребёнок B → серый\n              \u002F\u002F и новый ребёнок C → серый\n",[35,679,677],{"__ignoreMap":244},[31,681,682],{},"Стек НЕ покрыт barrier (дорого). Стеки сканируются целиком один раз при приостановке горутины.",[31,684,685],{},"Rescan стеков не нужен → паузы короче.",[31,687,688],{},[40,689,690],{},"Когда активен в Go",[31,692,693],{},"Mark phase: write barrier ON. Sweep phase: write barrier OFF.",[31,695,696],{},"Включение\u002Fвыключение требует STW (~10-30µs).",[31,698,699,702],{},[40,700,701],{},"Корнер-кейс: серые не заканчиваются."," Мутаторы плодят объекты → все красятся серым → очередь не пустеет.",[31,704,705],{},"Решение: после лимита красить новые объекты сразу чёрными. Мусор доживёт до следующего цикла, но фаза Mark завершится.",[31,707,708],{},[40,709,710],{},"Overhead",[31,712,713],{},"~2 доп. инструкции на запись указателя.",[31,715,716,717,719],{},"Fast path: проверка ",[35,718,598],{}," (почти бесплатно когда GC не активен).",[31,721,722],{},"Общий overhead: ~10-20% на pointer-heavy код.",[31,724,725],{},[40,726,727],{},"Эволюция Go",[31,729,730],{},"Go 1.5: insertion barrier → STW rescan стеков.",[31,732,733],{},"Go 1.8: hybrid barrier → нет rescan → короче паузы.",[23,735],{},[10,737,738,744,758],{},[13,739,740,743],{},[40,741,742],{},"GOGC",": % роста heap до следующего GC (default 100 → next GC = prev × 2). Трейдофф:  GOGC↓ = чаще GC, меньше памяти, больше CPU. GOGC↑ = реже GC, больше памяти, меньше CPU.",[13,745,746,749,750,753,754,757],{},[40,747,748],{},"GOMEMLIMIT",": soft-лимит на ",[40,751,752],{},"всю"," память, runtime сам уменьшает GOGC. Защита: CPU GC ",[40,755,756],{},"≤ 50%"," при  Если не справляется — позволяет превысить лимит (soft, не hard). Иначе deadlock.",[13,759,760,763],{},[40,761,762],{},"death spiral:"," live heap ≈ лимит\n→ GC постоянно работает\n→ CPU 100% на GC (но ≤50% с GOMEMLIMIT)\n→ программа не обрабатывает запросы\n→ запросы копятся → нужно ещё больше памяти → ...",[23,765],{},[31,767,768],{},"Два способа управления GC: частота (GOGC) и лимит памяти (GOMEMLIMIT).",[26,770,772],{"id":771},"gogc-частота-сборки","GOGC — частота сборки",[236,774,777],{"className":775,"code":776,"language":241},[239],"Процент роста heap до следующего GC. Default: 100\n\nnext GC = live heap × (1 + GOGC\u002F100)\n\nGOGC=100, live heap=100MB → GC при 200MB  (×2)\nGOGC=50,  live heap=100MB → GC при 150MB  (×1.5)\nGOGC=200, live heap=100MB → GC при 300MB  (×3)\n",[35,778,776],{"__ignoreMap":244},[31,780,781],{},"Трейдофф: GOGC↓ = чаще GC, меньше памяти, больше CPU. GOGC↑ = реже GC, больше памяти, меньше CPU.",[31,783,784,787],{},[40,785,786],{},"Проблема ручного подбора:"," значение зависит от нагрузки. Сегодня 50, завтра нужно 80, послезавтра 30. Вечная ручная подстройка.",[31,789,790,793],{},[40,791,792],{},"Пример OOM:"," VM с лимитом 10GB, GOGC=100. После GC heap = 5.1GB. Следующий GC при 5.1 × 2 = 10.2GB > лимит VM → OOM-killer убьёт процесс до срабатывания GC.",[26,795,797],{"id":796},"ballast-хак","Ballast-хак",[236,799,803],{"className":800,"code":801,"language":802,"meta":244,"style":244},"language-go shiki shiki-themes github-dark","var ballast = make([]byte, 2\u003C\u003C30)  \u002F\u002F 2GB\n","go",[35,804,805],{"__ignoreMap":244},[806,807,810,814,818,821,825,828,831,834,838,841,844,847],"span",{"class":808,"line":809},"line",1,[806,811,813],{"class":812},"snl16","var",[806,815,817],{"class":816},"s95oV"," ballast ",[806,819,820],{"class":812},"=",[806,822,824],{"class":823},"svObZ"," make",[806,826,827],{"class":816},"([]",[806,829,830],{"class":812},"byte",[806,832,833],{"class":816},", ",[806,835,837],{"class":836},"sDLfK","2",[806,839,840],{"class":812},"\u003C\u003C",[806,842,843],{"class":836},"30",[806,845,846],{"class":816},")  ",[806,848,850],{"class":849},"sAwPA","\u002F\u002F 2GB\n",[31,852,853,854,857],{},"Аллокация большого массива для поднятия порога GC. Работает из-за ",[40,855,856],{},"lazy allocation"," ОС: пока не пишем в память, RSS не растёт (только VSS). С появлением GOMEMLIMIT в 99% случаев не нужен.",[26,859,861],{"id":860},"gomemlimit-go-119-soft-memory-limit","GOMEMLIMIT (Go 1.19) — soft memory limit",[31,863,864],{},[40,865,861],{},[31,867,868,869,871],{},"Учитывает ",[40,870,752],{}," память (heap + runtime metadata), не только кучу. НЕ включает: memory-mapped files, cgo memory.",[31,873,874],{},"Runtime динамически крутит GOGC при приближении к лимиту.",[31,876,877,880],{},[40,878,879],{},"Защита от death spiral:"," CPU на GC ≤ 50%. Если не справляется — позволяет превысить лимит (soft, не hard). Иначе deadlock.",[236,882,885],{"className":883,"code":884,"language":241},[239],"# Рекомендация в контейнерах\nGOMEMLIMIT = container_limit * 0.9\n\n# Обычный режим\nGOGC=100 GOMEMLIMIT=2GiB\n\n# Максимум памяти, минимум GC\nGOGC=off GOMEMLIMIT=4GiB → GC только при приближении к лимиту\n\n# Агрессивный GC\nGOGC=50 GOMEMLIMIT=512MiB\n",[35,886,884],{"__ignoreMap":244},[31,888,889],{},[40,890,891],{},"Программно:",[236,893,895],{"className":800,"code":894,"language":802,"meta":244,"style":244},"debug.SetGCPercent(50)         \u002F\u002F GOGC=50\ndebug.SetGCPercent(-1)         \u002F\u002F выключить GC\ndebug.SetMemoryLimit(1 \u003C\u003C 30)  \u002F\u002F 1GiB\n",[35,896,897,917,937],{"__ignoreMap":244},[806,898,899,902,905,908,911,914],{"class":808,"line":809},[806,900,901],{"class":816},"debug.",[806,903,904],{"class":823},"SetGCPercent",[806,906,907],{"class":816},"(",[806,909,910],{"class":836},"50",[806,912,913],{"class":816},")         ",[806,915,916],{"class":849},"\u002F\u002F GOGC=50\n",[806,918,920,922,924,926,929,932,934],{"class":808,"line":919},2,[806,921,901],{"class":816},[806,923,904],{"class":823},[806,925,907],{"class":816},[806,927,928],{"class":812},"-",[806,930,931],{"class":836},"1",[806,933,913],{"class":816},[806,935,936],{"class":849},"\u002F\u002F выключить GC\n",[806,938,940,942,945,947,949,952,955,957],{"class":808,"line":939},3,[806,941,901],{"class":816},[806,943,944],{"class":823},"SetMemoryLimit",[806,946,907],{"class":816},[806,948,931],{"class":836},[806,950,951],{"class":812}," \u003C\u003C",[806,953,954],{"class":836}," 30",[806,956,846],{"class":816},[806,958,959],{"class":849},"\u002F\u002F 1GiB\n",[26,961,963],{"id":962},"death-spiral","Death spiral",[236,965,968],{"className":966,"code":967,"language":241},[239],"live heap ≈ лимит\n  → GC постоянно работает\n  → CPU 100% на GC (но ≤50% с GOMEMLIMIT)\n  → программа не обрабатывает запросы\n  → запросы копятся → нужно ещё больше памяти → ...\n",[35,969,967],{"__ignoreMap":244},[31,971,972],{},"Сложнее всего обнаружить: приложение работает, CPU загружен, но throughput нулевой.",[31,974,975],{},"С GOGC легко попасть при ручном подборе. С GOMEMLIMIT — защита через ограничение 50% CPU.",[23,977],{},[10,979,980,983,989,1003,1008],{},[13,981,982],{},"Pacer: завершить GC цикл ДО исчерпания heap target. Если запустить слишком поздно — heap вырастет больше цели.",[13,984,985,986],{},"формула (Go 1.18+): ",[35,987,988],{},"Target = Live heap + (Live heap + stacks + globals) × GOGC\u002F100",[13,990,991,994,995,998,999,1002],{},[40,992,993],{},"Go 1.18:"," добавили ",[40,996,997],{},"стеки"," и globals ",[40,1000,1001],{},"в формулу",". До этого маленький heap + большие стеки = GC запускался слишком редко",[13,1004,1005,1006],{},"принудительный GC: если не было GC > 2 минут (sysmon), или ",[35,1007,53],{},[13,1009,1010,1012],{},[35,1011,53],{},": если вызвать во время активного GC, по достижению фазы Sweep он запустится заново",[23,1014],{},[31,1016,1017],{},"GC Pacer — алгоритм выбора момента запуска GC.",[31,1019,1020,1023],{},[40,1021,1022],{},"Задача:"," завершить GC цикл ДО исчерпания heap target. Если запустить слишком поздно — heap вырастет больше цели.",[31,1025,1026],{},[40,1027,1028],{},"Формула heap goal (Go 1.18+):",[236,1030,1033],{"className":1031,"code":1032,"language":241},[239],"Target = Live heap + (Live heap + stacks + globals) × GOGC\u002F100\n",[35,1034,1032],{"__ignoreMap":244},[31,1036,1037,1040],{},[40,1038,1039],{},"Изменения в Go 1.18:"," учитывает стеки и globals (раньше только heap).",[31,1042,1043],{},"Лучше работает с маленькими heap — раньше маленький heap + большие стеки = GC запускался слишком редко.",[31,1045,1046,1049],{},[40,1047,1048],{},"Принудительный GC:"," если не было GC > 2 минут — запускает sysmon (компонент runtime).",[31,1051,1052,1055],{},[40,1053,1054],{},"runtime.GC():"," если вызвать во время активного GC, по достижению фазы Sweep он запустится заново.",[23,1057],{},[10,1059,1060,1063,1070,1073],{},[13,1061,1062],{},"проблема: горутина аллоцирует быстрее, чем GC маркирует → GC не успевает",[13,1064,1065,1066,1069],{},"решение: Pacer заставляет «виновную» горутину ",[40,1067,1068],{},"тратить часть времени"," на маркировку",[13,1071,1072],{},"CPU GC: базово 25%, с Mark Assist до ~30%",[13,1074,1075,1078],{},[35,1076,1077],{},"runtime\u002Ftrace"," покажет «mark assist» время для горутин",[23,1080],{},[31,1082,1083],{},"Mark Assist — принудительная помощь GC от горутин.",[31,1085,1086,1089],{},[40,1087,1088],{},"Проблема:"," горутина аллоцирует быстрее, чем GC успевает маркировать. Фаза Mark может не завершиться вовремя.",[31,1091,1092,1095],{},[40,1093,1094],{},"Решение:"," Pacer заставляет «виновную» горутину помогать маркировке.",[31,1097,1098],{},"Время помощи пропорционально объёму аллокаций горутины — кто больше мусорит, тот больше убирает.",[31,1100,1101],{},[40,1102,1103],{},"CPU на GC:",[31,1105,1106],{},"Базово GC использует ~25% CPU (жёстко зафиксировано).",[31,1108,1109],{},"С Mark Assist — до ~30%: некоторые горутины выполняют код маркировки вместо бизнес-логики.",[31,1111,1112],{},"Концептуально: потоки ОС могут выполнять как код мутатора (бизнес-логика), так и код сборки мусора. Mark Assist переключает горутину на код GC.",[31,1114,1115,1118,1119,1121],{},[40,1116,1117],{},"Диагностика:"," ",[35,1120,1077],{}," покажет «mark assist» время — видно, какие горутины сколько времени потратили на помощь GC.",[23,1123],{},[10,1125,1126,1133,1136],{},[13,1127,1128,1129,1132],{},"идея: большинство объектов умирают молодыми → сканируем молодых ",[40,1130,1131],{},"чаще",", старых — редко",[13,1134,1135],{},"пережил сборку → перемещается в старшее поколение. Старшие поколения сканируются реже",[13,1137,1138,1139,1142],{},"в Go ",[40,1140,1141],{},"нет поколений",": escape analysis кладёт короткоживущие на стек ≈ «молодое поколение», стек GC не трогает, а вот в Java почти все на хипе",[23,1144],{},[26,1146,231],{"id":1147},"идея-2",[31,1149,1150],{},"Разбить кучу на поколения по возрасту объектов. Большинство объектов удаляются молодыми (как в естественном отборе). Те, кто пережил сборку мусора, вероятно переживут и следующую.",[236,1152,1155],{"className":1153,"code":1154,"language":241},[239],"Поколение 0 (young):  частые сборки, маленький объём\nПоколение 1 (old):    редкие сборки, большой объём\nПоколение 2 (oldest): очень редко, только при нехватке памяти\n",[35,1156,1154],{"__ignoreMap":244},[26,1158,1160],{"id":1159},"как-работает","Как работает",[236,1162,1165],{"className":1163,"code":1164,"language":241},[239],"Создали X1..X8 → все в поколении 0\nGC поколения 0 → X1, X3, X4, X8 живые → переносим в поколение 1\nСоздали Y1..Y8 → в поколении 0\nGC поколения 0 → Y3, Y8 живые → переносим в поколение 1\n...\nКогда памяти не хватает → GC поколения 1 (заглянуть к «старикам»)\n",[35,1166,1164],{"__ignoreMap":244},[31,1168,1169],{},"Идея: если не критическое потребление памяти, проходим GC только по молодым. Если 10 раз прошли молодых и толком не очистили — заглядываем к старшим.",[26,1171,1173],{"id":1172},"почему-в-go-нет-поколений","Почему в Go нет поколений",[31,1175,1176],{},"Go через escape analysis старается класть максимум на стек. Короткоживущие объекты (обработка запроса → больше не нужны) с большой вероятностью окажутся на стеке, а не в хипе.",[31,1178,1179,1182],{},[40,1180,1181],{},"Стек ≈ «молодое поколение»:"," объекты создаются и умирают при выходе из функции, GC их не трогает.",[31,1184,1185,1186,1189],{},"В Java почти всё в хипе (",[35,1187,1188],{},"new"," → heap), поэтому поколения критически важны. В Go хип уже «отфильтрован» escape analysis.",[23,1191],{},[10,1193,1194,1201,1212,1219,1225],{},[13,1195,1196,1197,1200],{},"callback перед очисткой объекта GC. Минимум ",[40,1198,1199],{},"2 цикла GC"," для освобождения (1-й: объект unreachable → финалайзер в очередь, объект НЕ удаляется. 2-й: объект удаляется)",[13,1202,1203,1204,1207,1208,1211],{},"два кейса: 1) страховка закрытия дескрипторов (os.File уже имеет финалайзер), 2) освобождение ",[40,1205,1206],{},"CGO-памяти"," (вместо ручного ",[35,1209,1210],{},"C.free()",")",[13,1213,1214,1215,1218],{},"для стековых объектов ",[40,1216,1217],{},"не работает"," (GC не трогает стек, нужен escape на heap)",[13,1220,1221,1224],{},[35,1222,1223],{},"runtime.KeepAlive(obj)"," — гарантирует что объект жив до этой точки (защита от преждевременного финалайзера)",[13,1226,1227,1228,1231],{},"Go 1.24+: ",[35,1229,1230],{},"runtime.AddCleanup"," — замена SetFinalizer с меньшим количеством проблем",[23,1233],{},[31,1235,1236],{},"Функция, вызываемая когда объект становится unreachable. Использовать осторожно.",[31,1238,1239],{},[40,1240,1241],{},"Базовое использование:",[236,1243,1245],{"className":800,"code":1244,"language":802,"meta":244,"style":244},"obj := &MyResource{fd: openFile()}\nruntime.SetFinalizer(obj, func(o *MyResource) {\n    o.Close()\n})\n",[35,1246,1247,1270,1298,1309],{"__ignoreMap":244},[806,1248,1249,1252,1255,1258,1261,1264,1267],{"class":808,"line":809},[806,1250,1251],{"class":816},"obj ",[806,1253,1254],{"class":812},":=",[806,1256,1257],{"class":812}," &",[806,1259,1260],{"class":823},"MyResource",[806,1262,1263],{"class":816},"{fd: ",[806,1265,1266],{"class":823},"openFile",[806,1268,1269],{"class":816},"()}\n",[806,1271,1272,1275,1278,1281,1284,1286,1290,1293,1295],{"class":808,"line":919},[806,1273,1274],{"class":816},"runtime.",[806,1276,1277],{"class":823},"SetFinalizer",[806,1279,1280],{"class":816},"(obj, ",[806,1282,1283],{"class":812},"func",[806,1285,907],{"class":816},[806,1287,1289],{"class":1288},"s9osk","o",[806,1291,1292],{"class":812}," *",[806,1294,1260],{"class":823},[806,1296,1297],{"class":816},") {\n",[806,1299,1300,1303,1306],{"class":808,"line":939},[806,1301,1302],{"class":816},"    o.",[806,1304,1305],{"class":823},"Close",[806,1307,1308],{"class":816},"()\n",[806,1310,1312],{"class":808,"line":1311},4,[806,1313,1314],{"class":816},"})\n",[31,1316,1317],{},[40,1318,1319],{},"Как работает:",[236,1321,1324],{"className":1322,"code":1323,"language":241},[239],"1. GC находит unreachable объект с finalizer\n2. Объект НЕ удаляется, finalizer ставится в очередь\n3. Отдельная горутина выполняет finalizer\n4. Следующий GC удаляет объект\n   └── минимум 2 цикла GC для освобождения\n",[35,1325,1323],{"__ignoreMap":244},[31,1327,1328],{},[40,1329,1330],{},"Два основных кейса:",[31,1332,1333,1118,1336,1339,1340,1343,1344,1347],{},[40,1334,1335],{},"1. Страховка закрытия дескрипторов.",[35,1337,1338],{},"os.File"," в Go уже имеет ",[35,1341,1342],{},"runtime.SetFinalizer"," внутри — если забыли ",[35,1345,1346],{},"Close()",", финалайзер закроет файл. То же для connection, handler и любых ресурсов ОС.",[31,1349,1350,1353,1354,1356],{},[40,1351,1352],{},"2. Освобождение CGO-памяти."," Если через CGO аллоцируете память в C — поставьте финалайзер вместо того, чтобы просить программиста не забыть ",[35,1355,1210],{},". Go — язык с автоматической сборкой мусора, CGO-память тоже должна очищаться автоматически.",[31,1358,1359,1362],{},[40,1360,1361],{},"Стековые объекты:"," GC не трогает стек → финалайзер скорее всего не сработает для объектов, которые не escape на хип.",[31,1364,1365,1368],{},[40,1366,1367],{},"Проблемы:"," непредсказуемое время выполнения, не гарантировано при завершении программы, объект живёт +1 GC цикл, циклические ссылки с finalizer → утечка, порядок не определён.",[31,1370,1371],{},[40,1372,1373],{},"runtime.KeepAlive:",[236,1375,1377],{"className":800,"code":1376,"language":802,"meta":244,"style":244},"func process(f *File) {\n    fd := f.fd\n    \u002F\u002F после этой строки f больше не используется напрямую\n    \u002F\u002F GC может решить что f unreachable → запустить финалайзер → закрыть файл\n    \u002F\u002F ... долгая работа с fd ...\n    syscall.Read(fd, buf) \u002F\u002F финалайзер может закрыть файл ДО этого → ошибка!\n    runtime.KeepAlive(f)  \u002F\u002F говорит компилятору: f жив до этой точки, не давай GC забрать\n}\n",[35,1378,1379,1398,1408,1413,1418,1424,1439,1454],{"__ignoreMap":244},[806,1380,1381,1383,1386,1388,1391,1393,1396],{"class":808,"line":809},[806,1382,1283],{"class":812},[806,1384,1385],{"class":823}," process",[806,1387,907],{"class":816},[806,1389,1390],{"class":1288},"f",[806,1392,1292],{"class":812},[806,1394,1395],{"class":823},"File",[806,1397,1297],{"class":816},[806,1399,1400,1403,1405],{"class":808,"line":919},[806,1401,1402],{"class":816},"    fd ",[806,1404,1254],{"class":812},[806,1406,1407],{"class":816}," f.fd\n",[806,1409,1410],{"class":808,"line":939},[806,1411,1412],{"class":849},"    \u002F\u002F после этой строки f больше не используется напрямую\n",[806,1414,1415],{"class":808,"line":1311},[806,1416,1417],{"class":849},"    \u002F\u002F GC может решить что f unreachable → запустить финалайзер → закрыть файл\n",[806,1419,1421],{"class":808,"line":1420},5,[806,1422,1423],{"class":849},"    \u002F\u002F ... долгая работа с fd ...\n",[806,1425,1427,1430,1433,1436],{"class":808,"line":1426},6,[806,1428,1429],{"class":816},"    syscall.",[806,1431,1432],{"class":823},"Read",[806,1434,1435],{"class":816},"(fd, buf) ",[806,1437,1438],{"class":849},"\u002F\u002F финалайзер может закрыть файл ДО этого → ошибка!\n",[806,1440,1442,1445,1448,1451],{"class":808,"line":1441},7,[806,1443,1444],{"class":816},"    runtime.",[806,1446,1447],{"class":823},"KeepAlive",[806,1449,1450],{"class":816},"(f)  ",[806,1452,1453],{"class":849},"\u002F\u002F говорит компилятору: f жив до этой точки, не давай GC забрать\n",[806,1455,1457],{"class":808,"line":1456},8,[806,1458,1459],{"class":816},"}\n",[31,1461,1462],{},[40,1463,1464],{},"Когда использовать:",[236,1466,1469],{"className":1467,"code":1468,"language":241},[239],"✗ Закрытие файлов → лучше defer Close() (финалайзер = страховка)\n✗ Освобождение Go-памяти → GC сам\n✓ Страховка для CGO ресурсов\n✓ Отладка утечек\n",[35,1470,1468],{"__ignoreMap":244},[31,1472,1473],{},[40,1474,1475],{},"Очистка и новый API:",[236,1477,1479],{"className":800,"code":1478,"language":802,"meta":244,"style":244},"runtime.SetFinalizer(obj, nil)  \u002F\u002F убрать finalizer\n\n\u002F\u002F Go 1.24+: runtime.AddCleanup — меньше проблем\nruntime.AddCleanup(&obj, func(ptr *int) { \u002F* cleanup *\u002F }, &arg)\n",[35,1480,1481,1497,1503,1508],{"__ignoreMap":244},[806,1482,1483,1485,1487,1489,1492,1494],{"class":808,"line":809},[806,1484,1274],{"class":816},[806,1486,1277],{"class":823},[806,1488,1280],{"class":816},[806,1490,1491],{"class":836},"nil",[806,1493,846],{"class":816},[806,1495,1496],{"class":849},"\u002F\u002F убрать finalizer\n",[806,1498,1499],{"class":808,"line":919},[806,1500,1502],{"emptyLinePlaceholder":1501},true,"\n",[806,1504,1505],{"class":808,"line":939},[806,1506,1507],{"class":849},"\u002F\u002F Go 1.24+: runtime.AddCleanup — меньше проблем\n",[806,1509,1510,1512,1515,1517,1520,1523,1525,1527,1530,1533,1536,1539,1542,1544],{"class":808,"line":1311},[806,1511,1274],{"class":816},[806,1513,1514],{"class":823},"AddCleanup",[806,1516,907],{"class":816},[806,1518,1519],{"class":812},"&",[806,1521,1522],{"class":816},"obj, ",[806,1524,1283],{"class":812},[806,1526,907],{"class":816},[806,1528,1529],{"class":1288},"ptr",[806,1531,1532],{"class":812}," *int",[806,1534,1535],{"class":816},") { ",[806,1537,1538],{"class":849},"\u002F* cleanup *\u002F",[806,1540,1541],{"class":816}," }, ",[806,1543,1519],{"class":812},[806,1545,1546],{"class":816},"arg)\n",[23,1548],{},[1550,1551,1552],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":244,"searchDepth":919,"depth":919,"links":1554},[1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579],{"id":28,"depth":919,"text":29},{"id":56,"depth":919,"text":57},{"id":67,"depth":919,"text":68},{"id":104,"depth":919,"text":105},{"id":114,"depth":919,"text":115},{"id":149,"depth":919,"text":150},{"id":156,"depth":919,"text":157},{"id":179,"depth":919,"text":180},{"id":230,"depth":919,"text":231},{"id":247,"depth":919,"text":248},{"id":254,"depth":919,"text":255},{"id":300,"depth":919,"text":301},{"id":358,"depth":919,"text":231},{"id":373,"depth":919,"text":374},{"id":396,"depth":919,"text":397},{"id":408,"depth":919,"text":409},{"id":415,"depth":919,"text":416},{"id":505,"depth":919,"text":506},{"id":771,"depth":919,"text":772},{"id":796,"depth":919,"text":797},{"id":860,"depth":919,"text":861},{"id":962,"depth":919,"text":963},{"id":1147,"depth":919,"text":231},{"id":1159,"depth":919,"text":1160},{"id":1172,"depth":919,"text":1173},"advanced","md",{},"oop","\u002F04-advanced\u002F04-gc","memory-allocator",{"title":5,"description":244},"gc","04-advanced\u002F04-gc\u002Findex",[1590,1591,1592,572,742,1593],"GC","mark-sweep","tri-color","паузы","4abJbDhGx3ZInz0wmmutx0CbiyECLmUxZDpi5v8AfYQ",1776289836540]