Где хранятся переменные в 1С: в стеке или в куче?

Программист IT и автоматизация бизнеса
← На главную

Каждый разработчик на платформе 1С:Предприятие рано или поздно задается вопросом: как именно система управляет оперативной памятью под капотом? Когда мы пишем простейшую строчку кода а = 5;, где физически создаются эти данные? Хранятся ли они в стеке вызовов или отправляются в динамическую кучу (Heap)?

Понимание этих механизмов критически важно для проектирования высокопроизводительных систем, оптимизации работы серверов приложений и предотвращения утечек памяти в рабочих процессах rphost.exe. В этой статье мы подробно разберем, как устроена память в 1С на физическом уровне (C++), как работает виртуальная машина платформы и каким образом происходит управление временем жизни объектов.

Архитектура исполнения кода: байт-код и виртуальная машина

Для начала проанализируем, как именно платформа выполняет написанный нами код. Встроенный язык 1С не интерпретируется «на лету» прямо из текстового вида, как это происходит в некоторых других языках. Платформа работает по принципу предварительной компиляции:

  1. Перед запуском выполнения модулей (например, при открытии формы или вызове серверного метода) исходный код компилируется во внутренний промежуточный байт-код.
  2. Скомпилированные модули кэшируются в оперативной памяти для быстрого повторного использования последующими сессиями и вызовами.
  3. Исполнением этого байт-кода занимается специальная виртуальная машина стекового типа.

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

Как переменные представлены в физической памяти на уровне C++

Сама платформа «1С:Предприятие» написана преимущественно на языке C++. Из-за того, что в 1С используется мягкая (динамическая) типизация, переменные не могут храниться в памяти как простые низкоуровневые типы (как это реализовано в чисто компилируемых языках вроде C++ или Rust).

Любая переменная в 1С на физическом уровне представляет собой сложную структуру-обертку. В терминах C++ это универсальный контейнер, аналогичный типу Variant или внутреннему типу платформы tValue. Рассмотрим структуру этого контейнера подробнее:

Этот контейнер всегда содержит две ключевые части: дескриптор типа (указывающий, что именно сейчас находится внутри — Число, Строка, Булево, СправочникСсылка, Массив и т. д.) и само значение (или указатель на него в оперативной памяти).

Физическое размещение данных в памяти напрямую зависит от типа переменной:

  1. Для простых (примитивных) типов (Число, Булево, Неопределено, NULL): их фактическое значение упаковывается прямо внутрь этой структуры-контейнера на стеке виртуальной машины. То есть, когда мы пишем а = 5;, число 5 физически не требует выделения отдельного блока в динамической памяти (куче); оно лежит непосредственно внутри дескриптора на стеке. Это обеспечивает максимальную скорость работы с примитивами.
  2. Для сложных (ссылочных и объектных) типов (Массив, Структура, СправочникОбъект, ТаблицаЗначений): на стеке виртуальной машины в структуре-контейнере хранится только указатель (32-битный или 64-битный адрес в зависимости от разрядности процесса) и служебная информация. Само же "тело" объекта (его элементы, строки, свойства) создается и живет в динамической памяти — куче (Heap).

Управление памятью в 1С: Подсчет ссылок

В отличие от таких платформ, как Java, .NET (JVM/CLR) или Python, где за освобождение памяти отвечает фоновый трассирующий сборщик мусора (Garbage Collector), 1С исторически использует более простую и детерминированную стратегию управления временем жизни объектов — подсчет ссылок (Reference Counting).

Рассмотрим по шагам, как функционирует этот механизм:

  1. Каждому созданному в куче объекту платформа сопоставляет внутренний счетчик ссылок.
  2. При присваивании ссылки на объект новой переменной счетчик увеличивается на 1.
  3. При выходе переменной из области видимости (например, при завершении работы процедуры или функции) или при принудительном обнулении переменной (Переменная = Неопределено;) счетчик уменьшается на 1.
  4. Как только счетчик ссылок становится равным нулю, объект незамедлительно и гарантированно удаляется из кучи, освобождая занятую оперативную память.

Посмотрим на пример кода, иллюстрирующий этот процесс:


Процедура ПроверитьРаботуПамяти()
    
    // В куче создается объект структуры. Счетчик ссылок = 1.
    // На стеке создается переменная "ЛокальнаяСтруктура", хранящая адрес объекта.
    ЛокальнаяСтруктура = Новый Структура("Имя", "Алексей");
    
    // Счетчик ссылок увеличивается до 2.
    ВтораяСсылка = ЛокальнаяСтруктура;
    
    // Счетчик ссылок уменьшается до 1. Физически объект в куче все еще существует.
    ЛокальнаяСтруктура = Неопределено;
    
    // При завершении процедуры переменная "ВтораяСсылка" выходит из области видимости.
    // Счетчик ссылок становится равным 0. Объект структуры мгновенно уничтожается в куче.

КонецПроцедуры

Проблема циклических ссылок и утечек памяти (Memory Leaks)

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

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

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


Процедура СоздатьЦиклическуюСсылку()
    
    // Создаем два объекта в куче. У каждого счетчик ссылок равен 1.
    СтруктураА = Новый Структура;
    СтруктураБ = Новый Структура;
    
    // Создаем перекрестные ссылки между ними
    СтруктураА.Вставить("СсылкаНаБ", СтруктураБ); // Счетчик СтруктурыБ становится равен 2
    СтруктураБ.Вставить("СсылкаНаА", СтруктураА); // Счетчик СтруктурыА становится равен 2
    
    // Процедура завершается. Локальные переменные "СтруктураА" и "СтруктураБ" уничтожаются на стеке.
    // Счетчик ссылок СтруктурыА уменьшается с 2 до 1.
    // Счетчик ссылок СтруктурыБ уменьшается с 2 до 1.
    // Итог: объекты недоступны из кода, но продолжают жить в куче! Возникает утечка памяти.

КонецПроцедуры

Подобные "висячие" объекты остаются в куче процесса rphost.exe до момента перезапуска рабочего процесса. Если такие процедуры вызываются регулярно (например, в регламентных заданиях или при обработке большого количества транзакций пользователей), потребление памяти сервером приложений будет непрерывно расти, что в конечном итоге приведет к падению производительности или аварийному завершению работы системы.

Как бороться с утечками памяти в 1С:Практические рекомендации

Для минимизации рисков утечек памяти и повышения стабильности работы информационных баз, разработчикам рекомендуется придерживаться нескольких важных правил:

  1. Явное разрушение связей. Если вы проектируете сложные взаимосвязанные структуры данных (например, деревья, графы или двусвязные списки), обязательно предусматривайте процедуру явной очистки связей. Перед тем как уничтожить корневой объект, присваивайте всем дочерним ссылкам значение Неопределено.
  2. Использование встроенного анализатора. Начиная с версии платформы 8.3.10, разработчикам доступен инструмент «Проверка циклических ссылок встроенного языка» в Конфигураторе. Регулярно запускайте эту проверку для выявления потенциально опасных мест в вашем коде.
  3. Мониторинг технологического журнала. Настройте сбор событий технологического журнала, таких как LEAKS (информирует об утечках памяти при уничтожении контекстов) и CALL. Это позволит своевременно выявлять проблемные серверные вызовы, после которых память процесса rphost.exe не возвращается в исходное состояние.

Разберем пример правильного проектирования взаимодействия объектов без создания жестких взаимных ссылок в памяти. Вместо хранения ссылок на сами объекты в куче, зачастую эффективнее оперировать уникальными идентификаторами (например, УникальныйИдентификатор ссылки справочника), получая реальные объекты из базы данных только по мере необходимости.

← На главную