освобождение памяти для объектов, для которых я не установил значение null

РЕДАКТИРОВАТЬ: Проблема не была связана с вопросом. Это действительно было что-то не так с моим кодом, и на самом деле он был настолько простым, что я не хотел выкладывать его в Интернет. Спасибо, в любом случае.

Я прочитал примерно 550 тыс. записей Active Directory и сохранил их в списке, причем класс представляет собой простую оболочку для пользователя AD. Затем я разделил список ADRecords на четыре списка, каждый из которых содержал четверть от общего числа. После этого я прочитал около 400 тыс. записей из базы данных, известной как записи EDR, в таблицу данных. Я беру четыре четверти своего списка и создаю четыре потока, передавая каждую из четырех четвертей. Мне нужно сопоставить записи AD с записями EDR с помощью электронной почты прямо сейчас, но мы планируем добавить больше вещей для сопоставления позже.

У меня есть foreach в списке записей AD, и внутри этого я должен запустить цикл for для записей EDR, чтобы проверить каждую, потому что, если запись AD соответствует более чем одной записи EDR, то это не прямое соответствие и не должно рассматриваться как прямое соответствие.

Моя проблема заключается в том, что к тому времени, когда я доберусь до этого foreach в списке, в моем списке ADRecords будет всего около 130 записей, но сразу после того, как я их всех введу, я Console.WriteLine подсчитываю, и это 544k.

Я начинаю думать, что даже если я не установил для списка значение null, которое будет собрано позже, C# или Windows или что-то на самом деле забирают мой список, чтобы освободить место для записей EDR, потому что я не использовал список в пока. База данных, которую я должен использовать для чтения записей EDR, представляет собой связанный сервер, поэтому чтение их всех занимает около 10 минут, поэтому мой список фактически простаивает в течение 10 минут, но он никогда не устанавливается равным нулю.

Любые идеи?

//splitting list and passing in values to threads.
List<ADRecord> adRecords = GetAllADRecords();
        for (int i = 0; i < adRecords.Count/4; i++)
        {
            firstQuarter.Add(adRecords[i]);
        }
        for (int i = adRecords.Count/4; i < adRecords.Count/2; i++)
        {
            secondQuarter.Add(adRecords[i]);
        }
        for (int i = adRecords.Count/2; i < (adRecords.Count/4)*3; i++)
        {
            thirdQuarter.Add(adRecords[i]);
        }
        for (int i = (adRecords.Count/4)*3; i < adRecords.Count; i++)
        {
            fourthQuarter.Add(adRecords[i]);
        }
        DataTable edrRecordsTable = GetAllEDRRecords();

        DataRow[] edrRecords = edrRecordsTable.Select("Email_Address is not null and Email_Address <> ''", "Email_Address");
        Dictionary<string, int> letterPlaces = FindLetterPlaces(edrRecords);
        Thread one = new Thread(delegate() { ProcessMatches(firstQuarter, edrRecords, letterPlaces); });
        Thread two = new Thread(delegate() { ProcessMatches(secondQuarter, edrRecords,  letterPlaces); });
        Thread three = new Thread(delegate() { ProcessMatches(thirdQuarter, edrRecords,  letterPlaces); });
        Thread four = new Thread(delegate() { ProcessMatches(fourthQuarter, edrRecords, letterPlaces); });
        one.Start();
        two.Start();
        three.Start();
        four.Start();

В ProcessMatches есть foreach в списке переданных ADRecords. Первая строка в foreach — это AdRecordsProcessed++; который является глобальным статическим целым числом, и программа завершается с ним на 130 вместо 544 КБ.


person seekerOfKnowledge    schedule 08.10.2010    source источник
comment
ГК категорически этого не делает. Горе миру, если GC собирал объекты в использовании.   -  person Kirk Woll    schedule 08.10.2010
comment
Сомневайтесь в своем коде, а не в инструментах. Это хорошее место для начала диагностики таких проблем. Короткий, но полный пример кода, демонстрирующий проблему, был бы полезен.   -  person LBushkin    schedule 08.10.2010
comment
Забыл упомянуть, что однажды я добавил Console.WriteLine после того, как разделил их, проверив сумму в каждой из четырех и сложив вместе, на самом деле получился общий результат.   -  person seekerOfKnowledge    schedule 08.10.2010
comment
Что за компания с полумиллионом сотрудников?   -  person Hans Passant    schedule 08.10.2010
comment
@Hans Passant Индийские железные дороги имеют 1,6 миллиона сотрудников :) хотя я сомневаюсь, что они используют AD...   -  person AakashM    schedule 08.10.2010
comment
Последний комментарий, и если это не вызовет никаких дополнительных идей, то я собираю весь свой офис для мозгового штурма. Когда я вытаскиваю EDR с локального сервера, не связанного, он делает это быстро, полторы минуты или около того, и ВСЕ мои записи AD хранятся в памяти, но это не относится к связанному серверу, который занимает примерно 10 минут. минут.   -  person seekerOfKnowledge    schedule 08.10.2010
comment
@Hans - Федеральное правительство? :)   -  person Bryan    schedule 08.10.2010
comment
@Bryan - трудно представить, что они настолько организованы :) Wal-mart - крупнейший с 2,1 миллионами. Но та же проблема, что и на индийских железных дорогах, не у многих их сотрудников есть настольные компьютеры. Я бы предположил, что это Exxon Mobil или что-то подобное. Или просто поддельные тестовые данные.   -  person Hans Passant    schedule 08.10.2010
comment
@Hans - Клиент не так организован, и именно поэтому мне приходится делать эту программу.   -  person seekerOfKnowledge    schedule 09.10.2010


Ответы (4)


Переменная никогда не устанавливается в значение null и все еще находится в области видимости? Если это так, его не следует собирать, и время простоя не является вашей проблемой.

Первая проблема, которую я вижу, это:

AdRecordsProcessed++; 

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

Попробуйте запустить его из одного потока (т. е. передать adRecords вместо firstQuarter и не запускать другие потоки). Работает ли он должным образом с 1 потоком?

person Kendrick    schedule 08.10.2010
comment
Что делать с таким ответом? :) Похоже, вы задаете ОП вопрос, то есть комментарий. - person Kirk Woll; 08.10.2010
comment
Ответ на мой вопрос теперь есть в посте, но вы правы, вторая половина моего ответа должна была быть комментарием. - person Kendrick; 08.10.2010
comment
@jalf первая половина была ответом. время простоя не ваша проблема. Я оставил это на месте. - person Kendrick; 08.10.2010
comment
В ответ на вашу первую проблему вы не можете заблокировать целые числа. команда блокировки требует ссылочного типа. - person seekerOfKnowledge; 12.10.2010
comment
Я не пробовал, но я все еще предполагаю, что операция ++ не является потокобезопасной и поэтому может быть проблемой. Заблокируйте блок кода, который изменяет переменную, и тогда вы эффективно заблокируете переменную. В любом случае, я бы запустил его с одним потоком (возможно, с уменьшенным набором строк) и посмотрел бы, ведет ли он себя там. Если да, то проблема может заключаться в многопоточности. - person Kendrick; 12.10.2010
comment
статический объект только для чтения _locker = new object(); с этим на уровне класса я могу зафиксировать его вокруг своих операций ++, и мои числа будут более точными. Спасибо. - person seekerOfKnowledge; 13.10.2010
comment
В .NET 4.0 есть объект класса Interlocked, который гораздо лучше использовать, чем мой предыдущий комментарий. Класс Interlocked реализован на процессоре и доступен в .NET. Очень круто - person seekerOfKnowledge; 23.11.2010

Во-первых, вы не устанавливаете для списка значение null. Что вы можете сделать, так это установить для каждой ссылки на список значение null (или на другой список), или все такие ссылки могут просто выйти за рамки. Это может показаться придиркой, но если вам нужно изучить, что происходит с вашими данными, пришло время быть придирчивым к таким вещам.

Во-вторых, заставить сборщик мусора освободить что-то, что имеет активную ссылку, довольно сложно. Вы можете подделать его с помощью WeakReference‹> или подумать, что нашли его, когда столкнулись с ошибкой в ​​финализаторе (поскольку ссылка на самом деле не работает, и даже тогда финализатор пытается иметь дело с финализированным, а не чем освобожденный объект). Ошибки могут возникать где угодно, но то, что вы нашли способ заставить сборщик мусора освободить что-то, что работает, маловероятно.

GC, скорее всего, сделает две вещи с вашим списком:

  1. Вполне вероятно, что он уплотнит используемую им память, что приведет к перемещению его составных элементов.
  2. Вполне вероятно, что это продвинет его к более высокому поколению.

Ни в одном из них вы не обнаружите каких-либо изменений, если только не будете их искать (очевидно, вы заметите изменение в генерации, если продолжите вызывать GetGeneration(), но, кроме этого, вы на самом деле этого не сделаете).

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

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

Таким образом, хотя GC или ОС могут сделать что-то, чтобы освободить место для вашего другого объекта, это не является чем-то наблюдаемым в коде, и это не мешает объекту быть доступным и в том же программном состоянии.

Проблема в другом.

person Jon Hanna    schedule 08.10.2010
comment
Это почти стоит отрицательного голоса. GC почти наверняка не проблема, и ваш ответ предполагает, что это может быть. - person John Saunders; 08.10.2010
comment
@Джон, где я могу предположить, что GC может вызывать проблемы? Я объяснил, что то, что будет делать сборщик мусора, ничего не освобождает, и что если это каким-то образом произойдет, это вызовет сбой, а не уменьшение количества элементов, прежде чем сказать, что проблема заключается в чем-то другом. Что в этом говорит о том, что проблема в GC? - person Jon Hanna; 10.10.2010
comment
ОП искал проблему с его кодом и предположил, что проблема может быть в GC. Это почти наверняка было не так, поэтому я подумал, что вам не следовало упоминать GC в своем ответе. - person John Saunders; 13.10.2010
comment
@Джон. Поскольку я не знаю, в чем на самом деле проблема, довольно сложно не упомянуть GC при объяснении, почему это, вероятно, не проблема. - person Jon Hanna; 13.10.2010

Есть ли причина, по которой вам нужно получить все данные сразу? Если вы разбиваете данные на куски, они должны быть более управляемыми. Все, что я знаю, это то, что необходимость вникать в GC немного вонючая. Лучше всего посмотреть на рефакторинг вашего кода.

person Wix    schedule 08.10.2010
comment
Я полагаю, что это должно быть так. Я добавлю последний комментарий к вопросу, указав еще одну деталь, которая может поставить в тупик некоторых людей, но кто знает. - person seekerOfKnowledge; 08.10.2010

Сборщик мусора не соберет:

  • Глобальная переменная
  • Объекты, управляемые статическими объектами
  • Локальная переменная
  • Переменная, на которую может ссылаться любой метод в стеке вызовов

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

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

person Mike Hofer    schedule 08.10.2010