← Назад к вопросам

Что сделать, чтобы возникла утечка памяти?

1.6 Junior🔥 191 комментариев
#СУБД и хранение#Стандарты разработки

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Утечки памяти в 1С

Утечка памяти — это ситуация, когда программа продолжает выделять память, но не освобождает её, что приводит к исчерпанию ресурсов. В 1С утечки возникают реже, чем в языках с ручным управлением (C++), но они возможны. Вот полный обзор причин.

1. Циклические ссылки между объектами

Это классическая причина утечек в 1С:

// ПЛОХО - циклическая ссылка:

Объект1 = Новый Структура();
Объект2 = Новый Структура();

// Объект1 ссылается на Объект2
Объект1.Вставить("Ссылка", Объект2);

// Объект2 ссылается обратно на Объект1 (циклическая ссылка!)
Объект2.Вставить("ОбратнаяСсылка", Объект1);

// Даже если мы обнулим оба объекта,
// сборщик мусора может их не удалить
Объект1 = Неопределено;
Объект2 = Неопределено;
// Памяти всё ещё занимают!!! (утечка)

Почему это утечка?

Сборщик мусора 1С использует reference counting (подсчёт ссылок). Если объект ссылается сам на себя через другой объект, счётчик ссылок никогда не обнулится.

Решение:

// ХОРОШО - разорвать циклическую ссылку:

Объект1 = Новый Структура();
Объект2 = Новый Структура();

Объект1.Вставить("Ссылка", Объект2);
// НЕ создаём обратную ссылку!

Объект1 = Неопределено;
Объект2 = Неопределено;
// Память освобождена правильно

2. Глобальные переменные, которые растут

// ПЛОХО - глобальный массив растёт без контроля:

Переменная ГлобальныйКеш = Новый Массив();

Процедура ДобавитьВКеш(Значение)
    ГлобальныйКеш.Добавить(Значение);
    // Массив растёт с каждым вызовом
    // Но никогда не очищается!
КонецПроцедуры

// Спустя час работы сервера:
// ГлобальныйКеш содержит 1 млн элементов
// Память исчерпана

Решение:

// ХОРОШО - кеш с лимитом:

Переменная ГлобальныйКеш = Новый Соответствие();
Переменная МаксРазмерКеша = 1000;

Процедура ДобавитьВКеш(Ключ, Значение)
    
    // Если кеш переполнен — очистить половину
    Если ГлобальныйКеш.Количество() > МаксРазмерКеша Тогда
        // Удалить самые старые элементы (FIFO)
        Элементы = ГлобальныйКеш.Элементы();
        Для индекс = 1 По МаксРазмерКеша / 2 Цикл
            ГлобальныйКеш.Удалить(Элементы[индекс].Ключ);
        КонецЦикла;
    КонецЕсли;
    
    ГлобальныйКеш.Вставить(Ключ, Значение);
КонецПроцедуры

3. Большие объекты в памяти без освобождения

// ПЛОХО - таблица значений растёт:

Процедура ЗагрузитьВсеДанные()
    
    ГлобальнаяТаблица = Новая ТаблицаЗначений();
    ГлобальнаяТаблица.Колонки.Добавить("ID");
    ГлобальнаяТаблица.Колонки.Добавить("Данные");
    
    // Загрузить в памяти ВСЕ данные из БД (1 млн записей)
    Запрос = Новый Запрос("SELECT * FROM ТаблицаМиллион");
    ГлобальнаяТаблица = Запрос.Выполнить().Выгрузить();
    
    // Таблица занимает 500 МБ памяти
    // И держится всю сессию, даже если больше не нужна
КонецПроцедуры

Решение:

// ХОРОШО - обработать потоком, не держа всё в памяти:

Процедура ОбработатьДанные()
    
    Запрос = Новый Запрос("SELECT * FROM ТаблицаМиллион");
    Выборка = Запрос.Выполнить().Выбрать();
    
    // Обрабатываем потоком (по одной записи за раз)
    Пока Выборка.Следующий() Цикл
        // Обработать одну запись
        ID = Выборка.ID;
        Данные = Выборка.Данные;
        // Память использует только для текущей записи
    КонецЦикла;
    
    // После цикла все ресурсы освобождены
КонецПроцедуры

4. Неочищенные коллекции (Массив, Соответствие, Таблица значений)

// ПЛОХО - коллекция не очищена:

Процедура МояПроцедура()
    
    МояКоллекция = Новый Массив();
    
    Для индекс = 1 По 1000000 Цикл
        МояКоллекция.Добавить(Новый Структура("Данные", индекс));
    КонецЦикла;
    
    // Процедура закончилась, но МояКоллекция может остаться в памяти
    // если она глобальная переменная
КонецПроцедуры

Решение:

// ХОРОШО - явно очистить:

Процедура МояПроцедура()
    
    МояКоллекция = Новый Массив();
    
    Попытка
        Для индекс = 1 По 1000000 Цикл
            МояКоллекция.Добавить(Новый Структура("Данные", индекс));
        КонецЦикла;
    Исключение
        МояКоллекция = Неопределено; // очистить
        ВызватьИсключение;
    КонецПопытки;
    
    МояКоллекция = Неопределено; // явно очистить
КонецПроцедуры

5. Подписки на события не отписаны

// ПЛОХО - подписка без отписки:

Процедура ПриОкрытииФормы()
    // Подписка на событие
    ПодключитьОбработчикСобытия(ГлобальныйОбъект, "ПриИзменении", "МояОбработка");
    // Но отписка никогда не произойдёт!
    // Если форм открывается 100 раз, будет 100 подписок
    // Объект ГлобальныйОбъект не может быть удалён из памяти
КонецПроцедуры

Решение:

// ХОРОШО - отписать при закрытии:

Процедура ПриОкрытииФормы()
    ПодключитьОбработчикСобытия(ГлобальныйОбъект, "ПриИзменении", "МояОбработка");
КонецПроцедуры

Процедура ПриЗакрытииФормы()
    ОтключитьОбработчикСобытия(ГлобальныйОбъект, "ПриИзменении", "МояОбработка");
КонецПроцедуры

6. Кешированные объекты, которые устаревают

// ПЛОХО - кеш никогда не обновляется:

Переменная КешСправочников = Новый Соответствие();

Процедура ПолучитьСправочник(ИмяСправочника)
    
    Если КешСправочников.Содержит(ИмяСправочника) Тогда
        Возврат КешСправочников.Получить(ИмяСправочника);
    КонецЕсли;
    
    // Загрузить и кешировать
    Справочник = ЗагрузитьСправочник(ИмяСправочника);
    КешСправочников.Вставить(ИмяСправочника, Справочник);
    
    Возврат Справочник;
КонецПроцедуры

// Если справочник в БД изменился,
// кеш содержит старые данные
// И занимает память неопределённо долго

Решение:

// ХОРОШО - кеш с TTL (время жизни):

Переменная КешСправочников = Новый Соответствие();
Переменная ВремяЖизниКеша = 3600; // 1 час в секундах
Переменная ВремяОбновленияКеша = Новое Соответствие();

Процедура ПолучитьСправочник(ИмяСправочника)
    
    СейчасВремя = ТекущаяУниверсальнаяДата();
    
    Если КешСправочников.Содержит(ИмяСправочника) Тогда
        ВремяОбновления = ВремяОбновленияКеша.Получить(ИмяСправочника);
        ВремяПрошло = (СейчасВремя - ВремяОбновления) * 86400 * 1000; // в миллисекундах
        
        Если ВремяПрошло < ВремяЖизниКеша * 1000 Тогда
            Возврат КешСправочников.Получить(ИмяСправочника);
        КонецЕсли;
        
        // Кеш устарел — удалить
        КешСправочников.Удалить(ИмяСправочника);
        ВремяОбновленияКеша.Удалить(ИмяСправочника);
    КонецЕсли;
    
    // Загрузить и кешировать с отметкой времени
    Справочник = ЗагрузитьСправочник(ИмяСправочника);
    КешСправочников.Вставить(ИмяСправочника, Справочник);
    ВремяОбновленияКеша.Вставить(ИмяСправочника, СейчасВремя);
    
    Возврат Справочник;
КонецПроцедуры

7. Бесконечные коллекции в Синглтонах

// ПЛОХО - синглтон, который никогда не очищается:

Переменная ГлобальныйЛог = Новый Массив(); // глобальная переменная

Процедура ДобавитьВЛог(Сообщение)
    ГлобальныйЛог.Добавить(Сообщение); // растёт в памяти
КонецПроцедуры

// Спустя день сервера:
// ГлобальныйЛог содержит 10 млн записей
// Память исчерпана

Решение:

// ХОРОШО - логировать в БД, не в памяти:

Процедура ДобавитьВЛог(Сообщение)
    
    Запись = РегистрыСведений.Лог.СоздатьМенеджерЗаписи();
    Запись.Дата = ТекущаяДата();
    Запись.Сообщение = Сообщение;
    Запись.Записать();
    
    // Логируем в БД, память не занимает
КонецПроцедуры

8. Передача больших объектов по ссылке в рекурсии

// ПЛОХО - рекурсия с большими объектами:

Процедура РекурсивнаяОбработка(Данные, Глубина)
    
    Если Глубина > 1000 Тогда
        Возврат;
    КонецЕсли;
    
    // Каждый вызов рекурсии создаёт копию Данные в стеке
    РекурсивнаяОбработка(Данные, Глубина + 1);
    
КонецПроцедуры

// При глубине 1000:
// Стек содержит 1000 копий больших объектов
// Stack overflow и утечка памяти

Решение:

// ХОРОШО - итеративный подход:

Процедура ИтеративнаяОбработка(Корень)
    
    Стек = Новый Массив();
    Стек.Добавить(Корень);
    
    Пока Стек.Количество() > 0 Цикл
        Элемент = Стек[Стек.Количество() - 1];
        Стек.Удалить(Стек.Количество() - 1);
        
        // Обработать элемент
        // Добавить детей в стек если нужно
    КонецЦикла;
    
КонецПроцедуры

Как найти утечку памяти в 1С

// 1. Мониторинг памяти
// Администрирование → Рабочий процесс → Использование памяти

// 2. Профилирование
// В толстом клиенте: Сервис → Отладка → Профилировщик

// 3. Логирование объектов
// Добавить в код выписку информации об объектах:
Сообщить("Создан объект: " + ТипЗнч(МойОбъект));

// 4. Анализ циклических ссылок
// Найти все кросс-ссылки между объектами

Чеклист предотвращения утечек

  • Нет циклических ссылок между объектами
  • Глобальные переменные имеют лимит размера
  • Большие коллекции обрабатываются потоком
  • Подписки на события отписаны
  • Кеши имеют TTL (время жизни)
  • Логирование идёт в БД, не в память
  • Нет бесконечной рекурсии
  • Регулярная очистка глобальных объектов

Итого: утечки памяти в 1С возникают из циклических ссылок, глобальных переменных и неочищенных коллекций. Ключ к их избежанию — дизайн, подсчёт ссылок и явное освобождение ресурсов.