Проблемы с утечкой памяти: распоряжаться или не распоряжаться управляемыми ресурсами?

Я испытываю странную утечку памяти в вычислительном приложении .NET для поиска изображений на основе содержимого (CBIR).

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

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

Классы в проекте:

class Tag
{
    public Guid Id { get; set; }        // tag id
    public string Name { get; set; }    // tag name: e.g. 'sky','forest','road',...
    public byte[] Jpeg { get; set; }    // tag jpeg image patch sample
}

class IRepository
{
    public IEnumerable<Tag> FindAll();
}

class Service
{        
    private IDictionary<Guid, Tag> Cache { get; set; }  // to avoid frequent db reads
    // image capture background worker (ICBW)
    // image annotation background worker (IABW)
}

class Image
{
    public byte[] Jpeg { get; set; }
    public IEnumerable<Tag> Tags { get; set; }
}

Обработчик ICBW захватывает изображение в формате jpeg из некоторого источника изображения и передает его обработчику IABW для аннотации. IABW worker сначала пытается обновить Cache, если пришло время, а затем аннотирует изображение по некоторому алгоритму, создавая объект Image и прикрепляя к нему теги, а затем сохраняя его в хранилище аннотаций.

Фрагмент обновления кэша службы в рабочем процессе IABW:

IEnumerable<Tag> tags = repository.FindAll();
Cache.Clear();
tags.ForEach(t => Cache.Add(t.Id, t));

IABW вызывается много раз в секунду и довольно требователен к процессору.

Запустив его в течение нескольких дней, я обнаружил увеличение памяти в диспетчере задач. Используя Perfmon для отслеживания Process/Private Bytes и .NET Memory/Bytes во всех кучах, я обнаружил, что они со временем увеличиваются.

Поэкспериментировав с приложением, я обнаружил, что проблема заключается в обновлении кэша. Если он не обновляется, нет проблем с увеличением памяти. Но если обновление кеша происходит так часто, как раз в 1-5 минут, приложение довольно быстро освобождается от памяти.

В чем может быть причина утечки мемов? Довольно часто создаются объекты изображения, содержащие ссылки на объекты тегов в кэше. Я предполагаю, что когда создается словарь Cache, эти ссылки каким-то образом не собираются мусором в будущем.

Нужно ли явно обнулять управляемые объекты byte[], чтобы избежать утечки памяти, например. путем реализации тега, изображения как IDisposable?

Редактировать: 4 августа 2001 г., добавлен фрагмент кода с ошибками, вызывающий быструю утечку памяти.

static void Main(string[] args)
{
    while (!Console.KeyAvailable)
    {
        IEnumerable<byte[]> data = CreateEnumeration(100);
        PinEntries(data);
        Thread.Sleep(900);
        Console.Write(String.Format("gc mem: {0}\r", GC.GetTotalMemory(true)));
    }
}

static IEnumerable<byte[]> CreateEnumeration(int size)
{
    Random random = new Random();
    IList<byte[]> data = new List<byte[]>();
    for (int i = 0; i < size; i++)
    {
        byte[] vector = new byte[12345];
        random.NextBytes(vector);
        data.Add(vector);
    }
    return data;
}

static void PinEntries(IEnumerable<byte[]> data)
{
    var handles = data.Select(d => GCHandle.Alloc(d, GCHandleType.Pinned));
    var ptrs = handles.Select(h => h.AddrOfPinnedObject());
    IntPtr[] dataPtrs = ptrs.ToArray();
    Thread.Sleep(100); // unmanaged function call taking byte** data
    handles.ToList().ForEach(h => h.Free());
}

person Chesnokov Yuriy    schedule 03.08.2011    source источник


Ответы (1)


Нет, вам не нужно ничего обнулять или удалять что-либо, если это просто память, как вы показали.

Я предлагаю вам найти хорошего профилировщика, чтобы выяснить, где находится утечка. Есть ли у вас что-нибудь, не связанное с памятью, от чего вы не можете избавиться, например загрузка изображения GDI+ для получения байтов?

person Jon Skeet    schedule 03.08.2011
comment
Да, это просто байты изображений, сжатых в формате Jpeg. Я продолжаю использовать операторы {} для растрового изображения, созданного из jpeg в IABW, чтобы аннотировать его. - person Chesnokov Yuriy; 03.08.2011
comment
Нет утечки памяти, если Cache не обновляется. Я считаю, что это решает проблему с ожидающими дескрипторами растровых изображений. - person Chesnokov Yuriy; 03.08.2011
comment
@Chesnokov: В таком случае я бы разобрал профайлер. Попробуйте найти какой-нибудь способ заставить его очень быстро утечку памяти, чтобы вы могли внести изменения и посмотреть, что произойдет, не дожидаясь 24 часов и т. д. :) - person Jon Skeet; 03.08.2011
comment
:) нет необходимости ждать 24 часа, если обновление кэша происходит так быстро, как раз в 1-5 минут. В этом случае вы можете наблюдать рост памяти в Perfmon во время работы. Я думал об управляемых ссылках byte[] как о виновнике их явного обнуления. - person Chesnokov Yuriy; 03.08.2011
comment
@Chesnokov: Нет, если на них больше ничего не ссылается, это не должно иметь никакого значения. Я предлагаю вам обновлять кеш раз в секунду или около того, и посмотреть, не закончится ли у вас очень быстро память. Обратите внимание, что объем памяти может увеличиться на некоторое время, но он должен стабилизироваться. - person Jon Skeet; 03.08.2011
comment
нет, он не стабилизируется, быстро достигает 1 ГБ или около того, после чего OutOfMemory перехватывается (из-за 32-битной архитектуры), и я завершаю поток, чтобы избежать его работы в этом состоянии. Дальше тоже не выпускается - person Chesnokov Yuriy; 03.08.2011
comment
@Chesnokov: Верно, так что это действительно похоже на утечку. Если вы сможете придумать короткую, но полную программу, которая демонстрирует проблему, мы можем помочь больше... в противном случае это просто случай профилирования. - person Jon Skeet; 03.08.2011
comment
Да, это. Я надеюсь создать это в отдельном консольном приложении. Я попытался смоделировать это в коротком консольном приложении, но проблема не появилась. Я предполагаю, что мне нужно явно перенести бизнес-логику домена на отдельную консоль, надеюсь получить ее в ближайшее время. Было бы интересно посмотреть, связана ли проблема с GC - person Chesnokov Yuriy; 03.08.2011
comment
Спасибо за предложение обновлять кеш каждую секунду. Я создал простую консоль, которая обрабатывала изображение и обновляла базу данных в цикле, и память увеличивалась очень быстро. Я предполагаю, что подозреваемого в утечке памяти можно найти в stackoverflow.com/questions/6937933/ - person Chesnokov Yuriy; 04.08.2011
comment
Это не неуправляемый код, вызывающий утечку. Я наколдовал консоль, чтобы имитировать ошибку, прикрепив ее здесь ниже... - person Chesnokov Yuriy; 04.08.2011