Каждый разработчик на платформе 1С:Предприятие рано или поздно задается вопросом: как именно система управляет оперативной памятью под капотом? Когда мы пишем простейшую строчку кода а = 5;, где физически создаются эти данные? Хранятся ли они в стеке вызовов или отправляются в динамическую кучу (Heap)?
Понимание этих механизмов критически важно для проектирования высокопроизводительных систем, оптимизации работы серверов приложений и предотвращения утечек памяти в рабочих процессах rphost.exe. В этой статье мы подробно разберем, как устроена память в 1С на физическом уровне (C++), как работает виртуальная машина платформы и каким образом происходит управление временем жизни объектов.
Для начала проанализируем, как именно платформа выполняет написанный нами код. Встроенный язык 1С не интерпретируется «на лету» прямо из текстового вида, как это происходит в некоторых других языках. Платформа работает по принципу предварительной компиляции:
Таким образом, на уровне виртуальной машины 1С действительно существует собственный стек вызовов и стек вычислений. На этом стеке виртуальной машины обрабатываются команды, передаются параметры функций и выполняются математические и логические операции. Однако физическое представление этих данных в оперативной памяти компьютера имеет свою специфику.
Сама платформа «1С:Предприятие» написана преимущественно на языке C++. Из-за того, что в 1С используется мягкая (динамическая) типизация, переменные не могут храниться в памяти как простые низкоуровневые типы (как это реализовано в чисто компилируемых языках вроде C++ или Rust).
Любая переменная в 1С на физическом уровне представляет собой сложную структуру-обертку. В терминах C++ это универсальный контейнер, аналогичный типу Variant или внутреннему типу платформы tValue. Рассмотрим структуру этого контейнера подробнее:
Этот контейнер всегда содержит две ключевые части: дескриптор типа (указывающий, что именно сейчас находится внутри — Число, Строка, Булево, СправочникСсылка, Массив и т. д.) и само значение (или указатель на него в оперативной памяти).
Физическое размещение данных в памяти напрямую зависит от типа переменной:
Число, Булево, Неопределено, NULL): их фактическое значение упаковывается прямо внутрь этой структуры-контейнера на стеке виртуальной машины. То есть, когда мы пишем а = 5;, число 5 физически не требует выделения отдельного блока в динамической памяти (куче); оно лежит непосредственно внутри дескриптора на стеке. Это обеспечивает максимальную скорость работы с примитивами.Массив, Структура, СправочникОбъект, ТаблицаЗначений): на стеке виртуальной машины в структуре-контейнере хранится только указатель (32-битный или 64-битный адрес в зависимости от разрядности процесса) и служебная информация. Само же "тело" объекта (его элементы, строки, свойства) создается и живет в динамической памяти — куче (Heap).В отличие от таких платформ, как Java, .NET (JVM/CLR) или Python, где за освобождение памяти отвечает фоновый трассирующий сборщик мусора (Garbage Collector), 1С исторически использует более простую и детерминированную стратегию управления временем жизни объектов — подсчет ссылок (Reference Counting).
Рассмотрим по шагам, как функционирует этот механизм:
Переменная = Неопределено;) счетчик уменьшается на 1.Посмотрим на пример кода, иллюстрирующий этот процесс:
Процедура ПроверитьРаботуПамяти()
// В куче создается объект структуры. Счетчик ссылок = 1.
// На стеке создается переменная "ЛокальнаяСтруктура", хранящая адрес объекта.
ЛокальнаяСтруктура = Новый Структура("Имя", "Алексей");
// Счетчик ссылок увеличивается до 2.
ВтораяСсылка = ЛокальнаяСтруктура;
// Счетчик ссылок уменьшается до 1. Физически объект в куче все еще существует.
ЛокальнаяСтруктура = Неопределено;
// При завершении процедуры переменная "ВтораяСсылка" выходит из области видимости.
// Счетчик ссылок становится равным 0. Объект структуры мгновенно уничтожается в куче.
КонецПроцедуры
Несмотря на свою простоту и высокое быстродействие, механизм подсчета ссылок имеет один фундаментальный недостаток — уязвимость к образованию циклических ссылок. Выясним причину этой проблемы.
Если два объекта в куче ссылаются друг на друга напрямую (или опосредованно через цепочку других объектов), их счетчики ссылок никогда не смогут опуститься до нуля. Это происходит даже тогда, когда во встроенном языке 1С не осталось ни одной внешней переменной, указывающей на эти объекты.
Проанализируем классическую ситуацию возникновения циклической ссылки на примере кода:
Процедура СоздатьЦиклическуюСсылку()
// Создаем два объекта в куче. У каждого счетчик ссылок равен 1.
СтруктураА = Новый Структура;
СтруктураБ = Новый Структура;
// Создаем перекрестные ссылки между ними
СтруктураА.Вставить("СсылкаНаБ", СтруктураБ); // Счетчик СтруктурыБ становится равен 2
СтруктураБ.Вставить("СсылкаНаА", СтруктураА); // Счетчик СтруктурыА становится равен 2
// Процедура завершается. Локальные переменные "СтруктураА" и "СтруктураБ" уничтожаются на стеке.
// Счетчик ссылок СтруктурыА уменьшается с 2 до 1.
// Счетчик ссылок СтруктурыБ уменьшается с 2 до 1.
// Итог: объекты недоступны из кода, но продолжают жить в куче! Возникает утечка памяти.
КонецПроцедуры
Подобные "висячие" объекты остаются в куче процесса rphost.exe до момента перезапуска рабочего процесса. Если такие процедуры вызываются регулярно (например, в регламентных заданиях или при обработке большого количества транзакций пользователей), потребление памяти сервером приложений будет непрерывно расти, что в конечном итоге приведет к падению производительности или аварийному завершению работы системы.
Для минимизации рисков утечек памяти и повышения стабильности работы информационных баз, разработчикам рекомендуется придерживаться нескольких важных правил:
Неопределено.LEAKS (информирует об утечках памяти при уничтожении контекстов) и CALL. Это позволит своевременно выявлять проблемные серверные вызовы, после которых память процесса rphost.exe не возвращается в исходное состояние.Разберем пример правильного проектирования взаимодействия объектов без создания жестких взаимных ссылок в памяти. Вместо хранения ссылок на сами объекты в куче, зачастую эффективнее оперировать уникальными идентификаторами (например, УникальныйИдентификатор ссылки справочника), получая реальные объекты из базы данных только по мере необходимости.