Утечка памяти из-за оставшихся "внутренних" ссылок
Вложений: 1
Вступление
Истоки: возникла "необоснованная" утечка памяти. Ниже речь пойдёт о причине её возникновения и способе устранения. Рассматривать будем на "учебном" примере. Пусть у нас имеются три взаимосвязанных класса: 1. TData - для хранения данных 2. TDataSource - для хранения набора данных с типом TData 3. TDataManager - для хранения набора данных с типом TDataSource ЧАСТЬ 1: с памятью всё отлично. Классы вот такие: класс TData Код:
'класс для хранения данных класс TDataSource Код:
'класс для хранения набора данных типа TData и класс TDataManager Код:
'менеджер "ресурсов", все переменные глобальные (статические) Вот такой код: Код:
Strict Теперь у нас есть всё для запуска. Привожу весь код целиком. Код:
Strict И видим там такие значения: mem1 = 16246 mem2 = 8129054 mem3 = 16246 mem5 = 4073054 mem6 = 16246 Как видно, mem1 = mem3 = mem6, т.е. от чего уходили, к тому и приходили – использованная память освободилась. Так и должно быть. ЧАСТЬ 2: делаем очистку, но объём использованной памяти продолжает увеличиваться! Теперь давайте изменим код, а именно: добавим в класс TData поле parent с типом TDataSource. В этом поле будем хранить указатель на экземпляр TDataSource, давший жизнь нашему экземпляру TData. Код:
'класс для хранения данных Указатель на родителя будем присваивать в функции создания экземпляров TData, в классе TDataSource. Код:
'заполнение данными ("конструктор с параметром") Пришло время запустить выполнение программы и посмотреть результат в окне debuglog’а. А результат такой: mem1 = 16246 mem2 = 8137054 mem3 = 8136686 mem5 = 12197510 mem6 = 12197126 Как видно, результат сильно отличается от того, который был замечен в первой части. Тут mem1 <> mem3 <> mem6. Хотя сборщик мусора и поработал, но очистил менее 1 кб, а счёт шёл на мегабайты! Т.е. происходит утечка памяти! Разве так можно! (можно, но не нужно.) Объяснение: почему память не очищается? Т.к. мы лишь добавили поле parent, то естественно, всё дело в этом поле, точнее – в присвоении ему значения. Ведь что делает много(или мало?)уважаемый GC? Он удаляет из памяти те ресурсы, на которые в программе нет ссылок (=указателей) на момент его вызова. Вывод: ссылки на ресурсы есть. Но позвольте: мы же очистили список в менеджере – и все ссылки потеряли, не так ли!? Так, но «внутри» программы ссылки остались, хоть они больше и не доступны для использования. Подробнее: Присвоение полю parent надлежащего значения создает связь между экземпляром класса TData и экземпляром класса TDataSource, ведь суть поля parent – указатель на экземпляр класса TDataSource. А в списке list класса TDataManager хранятся тоже указатели на экземпляры класса TDataSource. Мало того – это указатели на одни и те же объекты. Т.е. указатели в списке менеджера и указатели полей parent указывают на одни и те же объекты! Вот оно! Да, при очистке списка в менеджере мы «зануляем» его указатели, однако хранимые в нём экземпляры содержат указатели на экземпляры TData (в своих списках list), а те экземпляры содержат указатели на родительские экземпляры TDataSorce в поле parent. Таким образом: список очищен, доступ к его элементам мы потеряли, однако указатели остались. А потому, GC их "оставляет", а как же иначе! Программное решение Задача: избавиться от связи экземпляров TData с экземплярами TDataSource. Чтобы это сделать, необходимо и достаточно удалить все экземпляры TData из всех экземпляров TDataSource. Делается это простой очисткой списков list для экземпляров TDataSource. Итак, добавим в класс TDataSource следующий метод: Код:
'функция удаления элементов, просто удаляем всё из списка Код:
'функция удаления элементов, просто удаляем всё из списков А результат явился таковым: mem1 = 16246 mem2 = 8137054 mem3 = 16246 mem5 = 4077054 mem6 = 16246 Что полностью совпадает с результатом, полученным в первой части статьи - вся память корректно освободилась. Итоги Рассмотрен случай с утечкой памяти, когда в памяти остаются «внутренние» ссылки объектов друг на друга. Ребят, люблю и тех, для кого всё вышесказанное – очевидность. Вот и всё. ;) |
Ответ: Утечка памяти из-за оставшихся "внутренних" ссылок
Добавлю что достаточно написать деструктор класса, для которого зарезервировано имя Delete:
Method Delete() 'очищаем все внутренние ресурсы чистим ссылки End Method Чем уникален деструктор? А тем что он автоматически вызывается для удаляемых объектов. ЧТо избавляет нас от ручного вызова функции очистки. Если уж сказал про деструктор скажу и про конструктор, за которым закреплено имя New: Method New() 'инициализация внутрених объектов, загрузка необходимых данных и т.д. End Method Этот метод автоматически вызывается при создании объекта. |
Ответ: Утечка памяти из-за оставшихся "внутренних" ссылок
Для моего примера я не смог очистить память с помощью Delete(), добавленного в класс TDataSource. А причина всё та же - список этого класса хранит данные с указателями на экземпляры этого же класса в поле parent, а потому данный метод не вызывается.
|
Ответ: Утечка памяти из-за оставшихся "внутренних" ссылок
Сборщик мусора у марка неумет обрабатывать перекрёстные ссылки, потому деструкторы не вызываются.
И в этом случае ваш вариант единственно правильный. |
Ответ: Утечка памяти из-за оставшихся "внутренних" ссылок
Гы....
А я то думаю что он деструкторы не вызывает?.... как я понимаю мы должны избавится от ВСЕХ ссылок на экземпляр чтобы GC вызвал деструктор и почистил память? |
Ответ: Утечка памяти из-за оставшихся "внутренних" ссылок
Товарищи!
У меня есть класс, в нем список элементов этого класса. Для удаления использую такую ф-ю: function Remove(C:Class) ListRemove(C,List) C=null GCCollect() endFunction все работло хорошо, но! для AI пришлось создать еще один класс, который мог содержать ссылку на объект класса Class. Как только вызывалась функция REmove() у того объекта, он выпадал из списка, на него терялась ссылка, но в том классе (ИИ) оставалась, поэтому GCCollect не удалял объект. Неужели придется при удалении объекта перелистывать все доступные ресурсы и обнулять в них ссылки на себя??????? |
Ответ: Утечка памяти из-за оставшихся "внутренних" ссылок
Вероятно, придётся.
Ещё есть понятие "паспорт объекта". Это по сути экземпляр объекта, содержащий дополнительные поля. Например так Код:
Type TObjectEx А при удалении присваиваешь bDeleted = true. Далее в том месте где обрабатываешь объекты, пробегая по циклу, пишешь Код:
for local obj:TObjectEx = eachin listObjs А может это изврат - создавать ещё один класс - и проще в классы (type) объектов поле bDeleted дописать и всё. Я лично вижу такой подход (наличие булевой переменной) применимым вполне, т.к. после однократного прохода по всем спискам, в которых есть ссылка на наш объект - эти ссылки удалятся, и получим то что хотим. |
Часовой пояс GMT +4, время: 19:00. |
vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot