last day (16 days later) » 

18:21
0
Q: Почему GC.Collect не собирает List помеченный как null?

Maxim GerasimovНа работе появилась задача раскопать завалы, лежащие в LOH. После некоторых попыток я обнаружил непонятное для себя поведение GC: public class Program { public static void Test() { List<int> list = new List<int>(1000000000); list = null; GC.Collect(); } pu...

Во-первых, GC не соберёт мусор, выделенный в этом же методе, в котором он вызван. То есть GC.Collect, вызванный внутри Test не уберёт list, созданный там же.
Во-вторых, для сборки LOH отдельные правила. См. GCSettings.LargeObjectHeapCompactionMode
@AlexanderPetrov не могли бы уточнить, почему сборщик не собирает мусор в том же методе, где была выделена память? Я нигде не слышал про такой нюанс.
По-моему, это нигде не документировано. Это из личного практического опыта. Сколько сам пытался также память очищать, сколько видел попытки других людей сделать аналогично - всегда безуспешно.
@AlexanderPetrov ладно, а то я уж думал, что упустил какую-то совсем очевидную вещь) Я знаю про правила сборки LOH. Интересно то, что при предварительном использовании Collect - следующие сборки не удаляют List, в противном случае успешно удаляют - почему? Ведь LOH уже не помечается ничем сверху, следовательно предварительное использование Collect не должно ни на что влиять.
ru.stackoverflow.com/a/592561/184217 - тут дополнительные советы.
18:21
@AlexanderPetrov я дополнил комментарий, если знаете - подскажите пожалуйста. Очень ценный опыт.
Если GC будет вызван извне метода Test, то сможет убрать list.. А когда он вызван внутри, то не может собрать list, но при этом проталкивает неубранный мусор выше в поколение. Вероятно, после этого list там и сидит.
@AlexanderPetrov правильно ли я понимаю, что хоть массив внутри списка и LOH, но сам лист вполне себе в SOH, поэтому после форсированного GC.Collect он проталкнулся в некст поколение, поэтому и не позволяет стереть LOH на который ссылается?
Возможно, так. Но тут я не могу уверенно сказать. Подождём, может какой гуру объявится.
@AlexanderPetrov я дополнил вопрос. Ваш ответ про вынесение Collect во "вне" не сработал.
Так у вас GCSettings.LargeObjectHeapCompactionMode в дефолтном значении или нет? По дефолту LOH же не компактится от слова совсем. Память может помечаться как свободная, но не будет освобождаться. В неё можно что-то новое положить, если влезет, но операционка её уже не получит. Я так понимаю. Да, и GC.Collect(2); пробовали, чтобы все поколения чистились, если дело в поколениях (что вряд ли)? И вообще чем и как вы смотрите, что очистил GC, а что нет?
18:21
@CrazyElf я пробовал, эффект тот же. И этот режим лишь говорит о том, что нужно сжать не отчищенную память. Этот режим никак не влияет на то, будет ли удален объект или нет.
@CrazyElf диспетчером задач/Visual Stuidio, этого достаточно тут. В выделенном состоянии этот list занимает 4 гига памяти, его тяжело не заметить) GC.Collect(2) не даст никакого эффекта - ибо массив внутри list лежит в LOH.
@MaximGerasimov Эээ, ну вот смотрите, у вас в LOH есть занятая память, потом есть 4Гб очищенной памяти, потом опять занятая память. Если не провести дефрагментацию, то эта память будет доступна для новых объектов, но GC её не отдаст системе. Конечно, если у вас в LOH только этот большой список и больше ничего, то может и всё нормально будет.
"Newly allocated objects form a new generation of objects and are implicitly generation 0 collections. However, if they are large objects, they go on the large object heap (LOH), which is sometimes referred to as generation 3. Generation 3 is a physical generation that's logically collected as part of generation 2." То есть LOH - это поколение 2. Нужно и флаг выставить CompactOnce и GC.Collect(2) вызвать. Ну либо если в системе будет мало памяти, GC может и сам LOH заняться, зависит от настроек, опять же.
Вообще очень интересная статья learn.microsoft.com/en-us/dotnet/standard/garbage-collection‌​/…
@CrazyElf ваш последний совет, как и следовало - не сработал.
Кстати, вот это while (true) ; может и понятно. GC возможно ждёт свободного процессора для своей работы, ну там sleep какой, а тут сплошной цикл крутится, фоновый GC думает, что процессор занят и не вступает в работу, чтобы не тормозить процессы. Попробуйте в цикл sleep добавить. А вообще добавьте в вопрос код что вы пробовали с флагами GCSettings и прочее, лишним не будет.
@CrazyElf ваше предположение на счет while(true) неверно для режима рабочей станции, так как в нем сборщик работает в фоновом режиме и ему все равно, что происходит внутри потоков. Вопрос дополнил.
А ещё попробуйте без отладки код запустить и посмотрите в диспетчере задач, что будет с памятью. Я пробовал, такое впечатление, что под отладкой никакие ухищрения не помогают память отдать, а без отладки наоборот и без ручных вызовов GC память спокойно освобождается. Хотя может в вашем случае будет по-другому. В целом, конечно, странно, что в теории вроде бы что-то понятно, а на практике всё не так.
18:21
@CrazyElf попробовал, тот же эффект) У меня вот тоже диссонанс. Вроде изучал эту тему, а на деле вот так.
Я тоже изучал эту тему, читал документацию, книги и разные статьи, в том числе Рихтера и Тауба. В целом у меня сложилось впечатление, что некоторые авторы сами не до конца разбираются в тонких нюансах. Полагаю, что сборщик мусора не будет освобождать память даже в тех случаях, когда ему прямо приказано это сделать, если он видит, что памяти хватает. Эвристик там внутри достаточно.

  last day (16 days later) »