Почему 1С потребляет всю оперативную память при обработке изображений и как это исправить

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При пакетной обработке графических файлов — повороте, изменении разрешения, склейке или динамическом предпросмотре — разработчики нередко сталкиваются с ситуацией, когда процесс платформы быстро увеличивает объем занимаемой оперативной памяти. Достигнув отметки около 4 ГБ, приложение аварийно завершает работу с ошибкой нехватки памяти. В подобных случаях подозрение часто падает на внешние компоненты (например, GFLAx или сторонние COM-библиотеки). Однако детальный анализ показывает, что корень проблемы чаще всего кроется в особенностях работы графической подсистемы самой платформы 1С.

Разберем природу этой проблемы, проанализируем типичный сценарий возникновения утечки и рассмотрим надежные способы её устранения.

Анализ проблемы: внешняя библиотека или платформа 1С

Рассмотрим стандартный сценарий: в цикле выполняется поворот файлов через COM-объект с последующим освобождением переменной:


КартинкаGFLAx = Новый COMОбъект("GFLAx.GFLAx");
КартинкаGFLAx.LoadBitmap(ПутьККартинке);
КартинкаGFLAx.Rotate(90);
КартинкаGFLAx.SaveBitmap(ПутьККартинке);
КартинкаGFLAx = Неопределено;

Если выполнять исключительно эту операцию, COM-объект корректно очищает дескрипторы памяти в операционной системе, и потребление ОЗУ процессом 1С остается стабильным. Однако на практике после модификации файла разработчик часто выводит результат пользователю на форму:


ВыбранноеФото = Новый Картинка(ПутьКФайлуКартинки, Ложь);
ЭлементыФормы.Предпросмотр.Картинка = ВыбранноеФото;
ВыбранноеФото = Неопределено;

Именно эта операция становится причиной утечки. Выясним причину такого поведения платформы:

  1. Ограничение 32-разрядного адресного пространства: 32-битный клиент 1С (даже работающий в 64-битной версии Windows) физически не может адресовать более 4 ГБ виртуальной памяти. При превышении этого лимита процесс мгновенно аварийно закрывается.
  2. Накопление дескрипторов GDI: При динамическом присвоении нового объекта Картинка в элемент формы графическая подсистема Windows выделяет системные дескрипторы (GDI-объекты HBITMAP). Внутри одного сеанса работы формы платформа 1С не всегда принудительно освобождает предыдущие растровые буферы, удерживая их до момента полного закрытия окна.
  3. Исчерпание лимита дескрипторов: В операционной системе Windows установлен жесткий лимит — не более 10 000 GDI-дескрипторов на один процесс. Если обработка содержит сотни или тысячи итераций, аварийное завершение может произойти задолго до достижения порога в 4 ГБ именно из-за исчерпания системных таблиц дескрипторов.

Способ 1. Уменьшение разрешения для области предпросмотра

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


ВремПутьККартинке = ПолучитьИмяВременногоФайла("jpg");

КартинкаGFLAx = Новый COMОбъект("GFLAx.GFLAx");
КартинкаGFLAx.LoadBitmap(ПутьКФайлуКартинки);

ШиринаКартинки = КартинкаGFLAx.Width;
ВысотаКартинки = КартинкаGFLAx.Height;

// Рассчитываем пропорциональное уменьшение до размеров области предпросмотра
Если ШиринаКартинки > ВысотаКартинки Тогда
    НужнаяШирина = 300;
    НужнаяВысота = Окр(ВысотаКартинки * (300 / ШиринаКартинки));
Иначе
    НужнаяВысота = 300;
    НужнаяШирина = Окр(ШиринаКартинки * (300 / ВысотаКартинки));
КонецЕсли;

КартинкаGFLAx.Resize(НужнаяШирина, НужнаяВысота);
КартинкаGFLAx.SaveBitmap(ВремПутьККартинке);
КартинкаGFLAx = Неопределено;

// Загружаем в форму облегченную копию
ВыбранноеФото = Новый Картинка(ВремПутьККартинке, Ложь);
ЭлементыФормы.Предпросмотр.Картинка = ВыбранноеФото;
ВыбранноеФото = Неопределено;

УдалитьФайлы(ВремПутьККартинке);

Уменьшение разрешения кадра в десятки раз пропорционально снижает объем памяти, выделяемой под растровый буфер интерфейсного элемента, что позволяет избежать резких скачков потребления ресурсов.

Способ 2. Явный сброс предыдущего изображения перед обновлением

Чтобы заставить контроллер формы принудительно освободить дескриптор предыдущего растра в памяти, перед установкой нового значения очистим поле пустым конструктором:


// Принудительно сбрасываем старый дескриптор в элементе управления
ЭлементыФормы.Предпросмотр.Картинка = Новый Картинка();

// Присваиваем новое изображение
ЭлементыФормы.Предпросмотр.Картинка = Новый Картинка(ПутьКФайлуКартинки, Ложь);

В сочетании с вызовом ОбработкаПрерыванияПользователя() в цикле это дает платформе возможность обработать очередь системных оконных сообщений и своевременно очистить освободившиеся дескрипторы.

Способ 3. Вывод через поле HTML-документа

Наиболее надежный интерфейсный способ избежать утечек при непрерывном показе графики — замена стандартного элемента формы на ПолеHTMLДокумента. Браузерный движок внутри элемента управления самостоятельно управляет жизненным циклом растровых объектов и корректно высвобождает память при смене источника:


ПутьДляHTML = СтрЗаменить(ПутьКФайлуКартинки, "\", "/");
ТекстHTML = "<html><body style='margin:0; padding:0; overflow:hidden;'>"
          + "<img src='file:///" + ПутьДляHTML + "' style='max-width:100%; max-height:100%; object-fit:contain;' />"
          + "</body></html>";

ЭлементыФормы.ПолеHTML.УстановитьТекст(ТекстHTML);

В этом случае платформа 1С не взаимодействует с графическими дескрипторами GDI напрямую, и память процесса не фрагментируется.

Способ 4. Разделение пакетной обработки и отрисовки интерфейса

При обработке массива файлов (сотен или тысяч штук) визуальное обновление картинки на каждой итерации цикла существенно замедляет общую скорость работы и провоцирует утечки памяти. Рекомендуется разделить логику:

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

Способ 5. Отказ от устаревших COM-объектов в пользу штатных средств

Если конфигурация работает на платформе версии 8.3.10 и выше, от использования внешней библиотеки GFLAx можно полностью отказаться в пользу встроенного объекта ОбрабатываемаяКартинка. Он доступен на стороне сервера, в толстом клиенте и не требует установки дополнительного стороннего ПО в операционной системе:


КартинкаИсточник = Новый Картинка(ПутьККартинке);

ОбработкаГрафики = Новый ОбрабатываемаяКартинка(КартинкаИсточник);
// Поворачиваем изображение на 90 градусов
ОбработкаГрафики.Повернуть(90);

// Получаем результирующий объект и сохраняем его на диск
ИтоговаяКартинка = ОбработкаГрафики.ПолучитьКартинку();
ИтоговаяКартинка.Записать(ПутьККартинке);

Объект ОбрабатываемаяКартинка также поддерживает штатное масштабирование через методы Масштабировать() и УстановитьРазмер(), кадрирование методом ВыделитьОбласть() и смену форматов через УстановитьФормат(), оптимизируя потребление ресурсов силами самой платформы.

Для систем, где применение штатного объекта невозможно, альтернативным решением является запуск консольных утилит (например, jpegtran) через метод ЗапуститьПриложение() с параметром ожидания завершения. При таком подходе трансформация файла выполняется в изолированном системном процессе, и вся выделенная память гарантированно возвращается операционной системе сразу после его закрытия.

← На главную