Во время разработки прикладного кода могут возникнуть утечки памяти, которые связаны с потенциальными ошибками или неэффективностью написания прикладного кода. Большинство случаев возникновения утечек связаны с использованием глобальных переменных, использованием типа данных Variant для хранения COM-объектов, хранением экземпляров объектов пользовательских классов в массивах или коллекциях.
Для выявления областей кода, которые приводят к утечкам, в среде разработки «Форсайт. Аналитическая платформа» реализован ряд дополнительных инструментов.
Примечание. Описываемые ниже флаги реестра и код должны использоваться только при создании и отладке приложения. Не должны применяться при промышленном использовании.
При отладке приложений в среде разработки периодически рекомендуется очищать среду выполнения Fore. Это гарантирует запуск и выполнение самой последней версии кода.
Для принудительной очистки среды выполнения Fore из среды разработки выполните команду главного меню «Отладка > Очистить среду выполнения Fore». После подтверждения выполняемого действия из памяти и кеша репозитория будут принудительно очищены неиспользуемые объекты, выгружены неиспользуемые сборки, выявлены возможные утечки памяти. Данное действие может привести к нарушению работы уже запущенных и не закрытых объектов. Рекомендуется перед выполнением закрыть все используемые объекты. Если после очистки в памяти останутся неосвобождённые объекты, то информация о них будет выведена в консоль среды разработки.
Инструмент позволяет сохранять граф оставшихся в памяти среды выполнения объектов. Имеется несколько вариантов сохранения графа, рассмотрим каждый из них.
Функциональность среды разработки может быть расширена за счет использования специальных параметров в реестре, описание которых представлено в подразделе «Расширенное логирование и получение дополнительной отладочной информации».
Для отслеживания утечек при выходе из репозитория создайте в ветке реестра [HKEY_CURRENT_USER\Software\Foresight\Foresight Analytics Platform\10.0\Fore]:
параметр CheckLeaks с типом REG_DWORD и значением 1;
параметр LeaksGraphFile и задайте в нём путь и наименование файла, в который будет сохраняться граф объектов.
Если при выходе из репозитория в памяти остаются неосвобождённые объекты, то граф с ними будет сохранён в указанный файл.
С помощью метода IForeRuntime.ForeObjectsGraph.SaveToFile можно сохранить список объектов, находящихся в памяти, в файл. Выведенные таким образом объекты не считаются утечками, а выводится список объектов находящихся в памяти на текущий момент выполнения кода приложения. Пример:
Sub UserProc;
Var
Mb: IMetabase;
Runtime: IForeRuntime;
// ...
Begin
Mb := MetabaseClass.Active;
// ...
// Выполняемый код приложения
// ...
Runtime := (Mb As IForeServices).GetRuntime;
Runtime.ForeObjectsGraph.SaveToFile("D:\CurrentObjects.tgf");
// ...
// Продолжение кода приложения
// ...
End Sub UserProc;
Список объектов сохраняется в формате TGF (trivial graph format). Для работы с файлом может использоваться любой редактор, поддерживающий просмотр и редактирование файлов в данном формате.
Пример графа:

Связь между MyClass и ArrayList обозначает, что в объекте типа MyClass есть непустое поле типа ArrayList. Так же, если объект класса может быть коллекцией в цикле For Each, то от коллекции до всех её элементов также отобразится связь. На примере это проявляется в том, что есть связь от объекта ArrayList до MyClass. Вершина Extern обозначает внешнюю ссылку. Если есть связь от Extern до объекта, то это означает, что есть внешняя ссылка на данный объект.
Инструмент позволяет сохранять идентификатор модуля/формы и номер строки, в которой произошло изменение счётчика внешних ссылок объектов. Запуск кода на выполнение должен осуществляться без отладки. Имеется несколько вариантов отслеживания изменения счётчика внешних ссылок, рассмотрим каждый из них.
Создайте в ветке реестра [HKEY_CURRENT_USER\Software\Foresight\Foresight Analytics Platform\10.0\Fore] Параметр ExternRefLogging типа REG_DWORD со значением «1». При инициализации среды выполнения будет запускаться логирование изменений счётчика внешних ссылок. Также в параметре ExtRefChangesFile укажите путь и наименование файла, в который будет сохраняться лог. Если при закрытии репозитория логирование оставалось включённым, то происходит автоматическое сохранение лога в заданный файл.
С помощью свойства IForeRuntime.ExternReferenceLogger.Enable можно включить логирование изменений счётчика внешних ссылок. Сохранение лога в файл осуществляется с помощью метода IForeRuntime.ExternReferenceLogger.SaveToFile. Сохранять можно после выключения логирования. Пример:
Sub UserProc;
Var
Mb: IMetabase;
ExtLogger: IExternReferenceLogger;
//...
Begin
Mb := MetabaseClass.Active;
ExtLogger := (Mb As IForeServices).GetRuntime.ExternReferenceLogger;
ExtLogger.Collapse := True;
ExtLogger.Enable := True;
ExtLogger.ObjectIndexing := True;
//...
// Выполняемый код приложения
//...
ExtLogger.Enable := False;
ExtLogger.SaveToFile("D:\ExtLog.txt");
//...
// Продолжение кода приложения
//...
End Sub UserProc;
Получаемый лог будет выглядеть примерно следующим образом:
MyClass Extern : 1
OBJ646 : 10 inc
OBJ646 : 10 inc
System : 4 dec
OBJ646 : 12 inc
OBJ646 : 12 inc
OBJ646 : 12 inc com
OBJ646 : 12 dec
OBJ646 : 14 inc
OBJ646 : 15 dec
In Garbage Collector : dec
ArrayList Extern : 1
OBJ646 : 11 inc
OBJ646 : 11 inc
System : 4 dec
OBJ646 : 13 inc
OBJ646 : 13 inc com
OBJ646 : 15 dec
Синтаксис лога в расширенной нотации РБНФ выглядит следующим образом:
Log = { LogRecord }
LogRecord = ClassName EXTERN ":" ExtRefCount { CodePosition ChangeType [ com ] }
ChangeType = "inc" | "dec"
CodePosition = (ModuleName ":" LineNumber | "In Garbage Collector" )
ClassName, ModuleName - идентификаторы, описывающие класс объекта и модуль, где происходит изменение внешней ссылки.
ExtRefCount, LineNumber - целые числа, описывающие счётчик внешних ссылок объекта и строчку кода, где изменилась внешняя ссылка.
ChangeType - тип изменения внешней ссылки: inc - увеличение; dec - уменьшение.
com - обозначение, используемое для отметки счётчика ссылок при работе с объектом пользовательского класса.
При выполнении какой-либо сборки без отладки интерпретатор языка загружает сборку в память и удерживает там пока:
на сборку имеются ссылки из других сборок;
имеются неуничтоженные объекты классов, определённых в этой сборке;
есть неуничтоженные объекты, предназначенные для работы с конструкциями сборки (IForeAssembly, IForeClass, IForeSub и другие).
При этом другие объекты, ссылающиеся на загруженную сборку, будут использовать именно ту её версию, которая загружена в текущий момент в память. Изменение кода такой сборки и её перекомпиляция не обновит сборку в памяти до завершения работы всего экземпляра продукта «Форсайт. Аналитическая платформа».
Во избежание подобной ситуации необходимо во время разработки использовать режим отладки, а также в соответствии с формируемой логикой приложения добавлять код для уничтожения объектов (Dispose), обнуления ссылок на объекты, освобождения статических (Shared) полей классов и переменных, используемых на уровне модулей. Также рекомендуется использовать команду главного меню среды разработки «Отладка > Очистить среду выполнения Fore», выполнение которой приводит к запуску полной сборки мусора, а также очищает содержимое статических полей и переменных в модулях. Если при завершении приложения в памяти остались какие-либо неуничтоженные объекты, то будет отображено окно «Объекты в памяти».
Ниже приведено несколько примеров кода, при выполнении которого возникнут утечки памяти. Избегайте возникновения подобных ситуаций в собственном коде.
Class MyClass: Object
Public v: Variant;
End Class MyClass;
Sub UserProc;
Var
o: MyClass;
ar: IArrayList;
Begin
o := New MyClass.Create;
ar := New ArrayList.Create;
ar.Add(o);
o.v := ar;
End Sub UserProc;
В данном примере в массив добавляется экземпляр класса и сам массив указывается в качестве значения поля класса. Сборщик мусора не сможет корректно обработать данную ситуация в результате чего в памяти останутся объекты.
Public Sub UserProc;
Var
o1, o2, o3: MyClass;
Begin
o1 := New MyClass.Create;
o2 := New MyClass.Create;
o3 := New MyClass.Create;
o1.v := o2;
o2.v := o3;
o3.v := o1;
End Sub UserProc;
В данном примере возникает циклическая зависимость объектов. Утечек памяти в данном случае не будет.
Public Sub UserProc;
Var
o1, o2, o3: MyClass;
ar: Array[1];
Begin
o1 := New MyClass.Create;
o2 := New MyClass.Create;
o3 := New MyClass.Create;
ar[0] := o2;
o1.v := ar[0];
o2.v := o3;
o3.v := o1;
End Sub UserProc;
А в данном примере будет утечка памяти из-за включения в цепочку дополнительного объекта в виде массива.
См. также: