Работа с памятью и сборка мусора
Работа с памятью и сборка мусора
Lua — язык с автоматическим управлением памятью. Программист не отвечает за выделение и освобождение объектов вручную, как в C или C++. Вместо этого Lua использует сборщик мусора (Garbage Collector, GC), который автоматически определяет и освобождает память, занятую объектами, более недоступными для программы.
Этот механизм позволяет сосредоточиться на логике приложения, но требует понимания его принципов: ведь даже автоматическая система может привести к утечкам, паузам или непредсказуемому поведению, если использоваться без учёта её особенностей.
Модель управления памятью
Lua использует гибридную модель управления памятью, сочетающую стек и кучу (heap).
Стек вызовов
Стек вызовов (call stack) хранит локальные переменные, параметры функций и информацию о возврате, управляется интерпретатором строго по принципу LIFO.
Объекты на стеке существуют только во время выполнения соответствующей функции. При выходе из функции локальные ссылки удаляются — но сами объекты (если они находятся в куче) могут сохраняться.
Важно: стек хранит значения примитивных типов (числа, булевы, nil) и указатели на объекты в куче (таблицы, функции, строки).
Куча
Куча (heap), централизованная область памяти, где размещаются все составные объекты: таблицы, функции (включая замыкания), потоки (coroutines), строки (неизменяемые, часто кэшируются).
Управление памятью в куче полностью делегировано сборщику мусора. Все объекты в куче управляются по ссылкам: переменные содержат не сами объекты, а ссылки на них.
local a = { x = 1 }
local b = a -- b ссылается на тот же объект в куче
b.x = 2
print(a.x) -- 2: изменение через одну ссылку видно через другую
Таким образом, Lua реализует ссылочную семантику для составных типов, аналогично Python, Java или JavaScript.
Жизненный цикл объекта
Жизненный цикл объекта в куче включает несколько этапов:
- Выделение — при создании (
{},function(),coroutine.create()). - Использование — объект доступен через одну или несколько ссылок.
- Недоступность — все прямые и косвенные ссылки на объект утеряны.
- Сборка мусора — GC помечает объект как "мертвый" и освобождает память.
- Финализация (опционально) — если у объекта есть метод
__gc, он вызывается перед освобождением.
Управление сборщиком мусора
Сборщик мусора работает невидимо для большинства программ, но его поведение можно анализировать, настраивать и даже принудительно запускать.
Lua использует incremental mark-and-sweep garbage collector — это означает, что процесс сборки разбит на этапы и выполняется по частям между шагами исполнения программы.
GC работает в два этапа:
- Mark (пометка). GC начинает с корней — глобальных переменных, локальных переменных в активных стеках, регистров VM, рекурсивно помечает все объекты, достижимые по ссылкам. Каждый достижимый объект получает флаг «жив».
- Sweep (очистка). Проход по всем объектам в куче, непомеченные объекты (недостижимые) освобождаются, а память возвращается системе или пулу. Для объектов с метаметодом
__gcвызывается финализатор на следующем цикле GC, чтобы избежать проблем с порядком удаления.
Этапы mark и sweep выполняются порциями, чтобы минимизировать паузы. Это критично для реального времени (например, игры), и называется инкрементальность.
GC запускается автоматически, когда объём выделённой памяти превышает порог, рассчитываемый на основе предыдущего объёма живых объектов и коэффициентов сборки.
Нужно ли вызывать GC вручную? Функция collectgarbage() позволяет взаимодействовать с GC явно:
collectgarbage("collect") -- Принудительный запуск полного цикла GC
collectgarbage("count") -- Возвращает текущий объём памяти в КБ
collectgarbage("step", step) -- Выполнить один шаг сборки
collectgarbage("stop") -- Остановить GC
collectgarbage("restart") -- Возобновить GC
Когда стоит использовать принудительный вызов?
- После загрузки ресурсов (например, уровня в игре), чтобы очистить временные данные.
- Перед критическими участками (анимация, ввод), чтобы минимизировать паузы GC.
- Для профилирования — отслеживание роста памяти.
И соответственно, не нужно в обычном потоке выполнения (GC и так работает эффективно), а частые вызовы collectgarbage("collect") могут ухудшить производительность, так как нарушают адаптивность GC. Используйте ручной GC целенаправленно и редко, как инструмент управления пиковыми нагрузками.
Ссылки
Все составные типы в Lua передаются и хранятся по ссылке.
Это означает, что:
- Присваивание таблицы не создаёт копию:
local a = { x = 1 }
local b = a
Теперь a и b указывают на один объект. Удаление a не освободит память, пока b существует.
- Замыкания захватывают переменные по ссылке:
function make_counter()
local count = 0
return function() count = count + 1; return count end
end
Объект count будет жить до тех пор, пока живёт замыкание. Таким образом, главная причина утечек памяти в Lua — непреднамеренное удержание ссылок.
Lua предоставляет механизм слабых ссылок через слабые таблицы, создаваемые с помощью метаметода __mode.
local weak_k = {}
setmetatable(weak_k, { __mode = "k" }) -- слабые ключи
local weak_v = {}
setmetatable(weak_v, { __mode = "v" }) -- слабые значения
local weak_kv = {}
setmetatable(weak_kv, { __mode = "kv" }) -- слабые ключи и значения
Как работают слабые ссылки? Слабая ссылка не предотвращает сборку мусора объекта. Если объект достигается только через слабые ссылки, он считается недостижимым и будет собран.
Пример - кэш объектов:
local cache = setmetatable({}, { __mode = "v" })
function get_or_create(key)
if not cache[key] then
cache[key] = expensive_creation(key)
end
return cache[key]
end
Здесь значения (объекты) будут автоматически удаляться GC, если на них нет других ссылок. Это позволяет строить автоматически очищающиеся кэши.
Слабые ссылки работают только для таблиц и userdata. Строки и числа не участвуют в GC (строки — иммутируемые, часто interned).
Финализаторы
Объекты типа userdata (и таблицы, если включено __gc) могут иметь финализатор — код, выполняемый перед освобождением.
local obj = newproxy(true) -- userdata-like object
getmetatable(obj).__gc = function(self)
print("Объект уничтожается")
end
Финализаторы вызываются не сразу после потери доступности, а на следующем цикле GC. Не стоит полагаться на них для освобождения внешних ресурсов (файлы, сокеты) — лучше делать это явно. Финализаторы могут создавать новые ссылки на объект — тогда он воскрешается (resurrection) и не будет удалён.
Lua предоставляет простые, но эффективные средства для мониторинга использования памяти.
- collectgarbage("count") возвращает текущий объём используемой памяти в килобайтах (с плавающей точкой):
print(collectgarbage("count")) -- например, 123.456
Можно использовать для измерения потребления памяти до/после операции и для поиска утечек: если значение постоянно растёт без плато.
- Дополнительные метрики:
-- Общий объём памяти (в КБ)
collectgarbage("count")
-- Количество вызовов GC
collectgarbage("stat") -- в некоторых реализациях (например, LuaJIT)
-- Настройка поведения GC
collectgarbage("setpause", 110) -- % использования до следующего цикла
collectgarbage("setstepmul", 200) -- скорость сборки относительно аллокации
- Внешние инструменты:
- luatrace — трассировка выделений.
- glue.gccount (в наборах расширений) — детальный подсчёт.
- Интеграция с debug-версиями движков (например, в Love2D или Nginx + OpenResty).
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Lua — это компактный, быстрый, встраиваемый интерпретируемый язык программирования высокого уровня, разработанный с акцентом на простоту, гибкость и эффективность. Набор советов, правил, принципов и обычаев в разработке на этом языке. LÖVE (Love2D) - 2D-движок для создания игр на Lua, кроссплатформенный, имеет простой API (love.load(), love.update(dt), love.draw()), используется инди-разработчиками и для обучения. Lua 5.1 (2006) — стабильная, самая распространённая версия. Используется в World of Warcraft, Nginx, многих движках. Гайд по установке и настройке с написанием первой программы и её запуском. Кавычки, точки, запятые, скобки и прочие знаки препинания. Lua использует двадцать два зарезервированных ключевых слова. Все они являются частью синтаксиса языка и недоступны для использования в качестве идентификаторов. Набор функций, которые включены в стандартную библиотеку языка. Типизация, набор правил определения типа данных значений языка. Lua предоставляет две формы условной конструкции — if-then-else и её компактный аналог через and/or, хотя последний используется с осторожностью из-за семантических различий. Анонимные функции (или лямбда-выражения) — это функции без имени, которые могут быть определены inline. Они особенно полезны при передаче в качестве аргументов или при создании замыканий. Объектно-ориентированное программирование (ООП) — это парадигма программирования, которая организует код вокруг объектов, объединяющих данные и поведение. В языке Lua отсутствует встроенная поддержка…Основы языка Lua
Рекомендации по разработке на Lua
Экосистема приложений на Lua
История языка Lua
Первая программа на Lua
Синтаксис и пунктуация в Lua
Ключевые слова языка Lua
Встроенные функции и стандартная библиотека Lua
Типы данных и объявление переменных в Lua
Управляющие конструкции и циклы в Lua
Функции, замыкания и анонимные функции
Объектно-ориентированное программирование в Lua