Когда можно позвонить в GC.Collect?
Общий совет, что вы не должны звонить GC.Collect
из вашего кода, но каковы исключения из этого правила?
Я могу думать только о нескольких очень конкретных случаях, когда имеет смысл форсировать сборку мусора.
Один пример, который приходит на ум, - это служба, которая периодически просыпается, выполняет какую-то задачу, а затем спит в течение длительного времени. В этом случае может быть хорошей идеей принудительно выполнить сбор, чтобы предотвратить удержание процесса, который скоро будет бездействующим, большего объема памяти, чем необходимо.
Есть ли другие случаи, когда можно позвонить GC.Collect
?
22 ответа
Если у вас есть веские основания полагать, что значительный набор объектов, особенно тех, которые, как вы подозреваете, относятся к поколениям 1 и 2, теперь имеют право на сборку мусора, и сейчас самое подходящее время для сбора с точки зрения небольшого снижения производительности.,
Хороший пример этого - если вы только что закрыли большую форму. Вы знаете, что теперь все элементы управления пользовательским интерфейсом могут быть собраны мусором, и очень короткая пауза, когда форма закрыта, вероятно, не будет заметна пользователю.
ОБНОВЛЕНИЕ 2.7.2018
Начиная с.NET 4.5 - есть GCLatencyMode.LowLatency
а также GCLatencyMode.SustainedLowLatency
, При входе и выходе из любого из этих режимов рекомендуется принудительно выполнить полный сбор данных с GC.Collect(2, GCCollectionMode.Forced)
,
Начиная с.NET 4.6 - есть GC.TryStartNoGCRegion
метод (используется для установки значения только для чтения GCLatencyMode.NoGCRegion
). Это может само по себе выполнить полную блокировку сборки мусора, пытаясь освободить достаточно памяти, но, учитывая, что мы запрещаем сборку мусора в течение некоторого периода, я бы сказал, что это также хорошая идея, чтобы выполнить полную сборку мусора до и после.
Источник: инженер Microsoft Бен Уотсон: написание высокопроизводительного кода.NET, 2-е изд. 2018.
Увидеть:
Я использую GC.Collect
только при написании грубых тестов производительности / профилировщика; т.е. у меня есть два (или более) блока кода для тестирования - что-то вроде:
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...
Чтобы TestA()
а также TestB()
работать с максимально похожим состоянием - т.е. TestB()
не забивают только потому, что TestA
оставил это очень близко к переломному моменту.
Классическим примером будет простой консольный exe (Main
например, достаточно сортировать, чтобы показать здесь), который показывает разницу между конкатенацией циклических строк и StringBuilder
,
Если мне нужно что-то точное, то это будут два совершенно независимых теста - но часто этого достаточно, если мы просто хотим минимизировать (или нормализовать) GC во время тестов, чтобы получить грубое представление о поведении.
Во время производства кода? Я еще не использовал это;-p
В большинстве случаев лучше не форсировать сборку мусора. (Каждая система, над которой я работал, имела принудительную сборку мусора, подчеркивала проблемы, которые в случае ее устранения устраняли бы необходимость принудительной сборки мусора и значительно ускоряли работу системы.)
Есть несколько случаев, когда вы знаете больше об использовании памяти, чем сборщик мусора. Это вряд ли может быть правдой для многопользовательского приложения или службы, которая отвечает более чем на один запрос за раз.
Однако в некоторых типах обработки вы знаете больше, чем GC. Например, рассмотреть приложение, которое.
- Дан список имен файлов в командной строке
- Обрабатывает один файл, а затем записывает результат в файл результатов.
- Во время обработки файла создается множество связанных объектов, которые невозможно собрать, пока не завершится обработка файла (например, дерево разбора).
- Не сохраняет состояние соответствия между файлами, которые он обработал.
Вы можете проверить (после тщательного) тестирования, что вам следует принудительно выполнить полную сборку мусора после обработки каждого файла.
В других случаях это служба, которая просыпается каждые несколько минут для обработки некоторых элементов и не сохраняет состояния, пока она спит. Тогда стоит начинать сбор полной коллекции перед сном.
Единственный раз, когда я рассмотрел бы форсирование коллекции, это когда я знаю, что много объектов было создано недавно, и на очень немногие объекты в настоящее время ссылаются.
Я бы предпочел иметь API для сборки мусора, когда мог бы давать подсказки об этом типе вещей, не заставляя себя собирать GC.
См. Также " Производительность Рико Мариани "
Один случай, когда вы пытаетесь выполнить модульное тестирование кода, который использует WeakReference.
В больших системах 24/7 или 24/6 - системах, которые реагируют на сообщения, запросы RPC или которые непрерывно опрашивают базу данных или обрабатывают - полезно иметь способ выявления утечек памяти. Для этого я склонен добавлять в приложение механизм, который временно приостанавливает любую обработку и затем выполняет полную сборку мусора. Это переводит систему в состояние покоя, где оставшаяся память либо является законно долгоживущей памятью (кэшами, конфигурацией и т. Д.), Либо "протекает" (объекты, которые не ожидаются или не желают быть укорененными, но фактически являются).
Наличие этого механизма значительно упрощает профилирование использования памяти, поскольку отчеты не будут затуманены шумом от активной обработки.
Чтобы убедиться, что вы получили весь мусор, вам нужно выполнить две коллекции:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Поскольку первая коллекция вызовет завершение любых объектов с финализаторами (но не сборщиком мусора эти объекты). Второй сборщик мусора будет собирать эти завершенные объекты.
Вы можете вызвать GC.Collect(), когда знаете что-то о природе приложения, которого не знает сборщик мусора. Заманчиво думать, что, как автор, это очень вероятно. Однако правда в том, что GC представляет собой довольно хорошо написанную и протестированную экспертную систему, и редко вы узнаете что-то о низкоуровневых путях кода, которых нет.
Лучший пример, который я могу вспомнить, где у вас может быть какая-то дополнительная информация, - это приложение, которое циклически переключается между периодами простоя и очень занятыми периодами. Вы хотите максимально возможную производительность в периоды занятости и, следовательно, хотите использовать время простоя для некоторой очистки.
Тем не менее, в большинстве случаев GC достаточно умен, чтобы сделать это в любом случае.
Один случай, когда почти необходимо вызвать GC.Collect(), - это автоматизация Microsoft Office через Interop. COM-объекты для Office не любят автоматически освобождаться и могут привести к тому, что экземпляры продукта Office будут занимать очень большие объемы памяти. Я не уверен, если это проблема или дизайн. В интернете много постов на эту тему, поэтому я не буду вдаваться в подробности.
При программировании с использованием Interop каждый отдельный COM-объект должен быть освобожден вручную, обычно с использованием Marshal.ReleseComObject(). Кроме того, вызов Garbage Collection вручную может помочь немного "очиститься". Вызов следующего кода, когда вы закончите с объектами Interop, кажется, очень помогает:
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
По моему личному опыту, использование комбинации ReleaseComObject и ручного вызова сборки мусора значительно уменьшает использование памяти продуктами Office, в частности Excel.
Как решение для фрагментации памяти. Я получал исключения из памяти при записи большого количества данных в поток памяти (чтение из сетевого потока). Данные были записаны в 8K кусках. После достижения 128M было исключение, хотя было много доступной памяти (но она была фрагментирована). Вызов GC.Collect() решил проблему. Я смог справиться с 1G после исправления.
Я делал некоторые тесты производительности на массив и список:
private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
var lstNumbers = new List<int>();
for(var i = 1; i <= count; i++)
{
lstNumbers.Add(i);
}
return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
var lstNumbers = new int[count];
for (var i = 1; i <= count; i++)
{
lstNumbers[i-1] = i + 1;
}
return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
return Enumerable.Range(1, count).ToArray();
}
static void performance_100_Million()
{
var sw = new Stopwatch();
sw.Start();
var numbers1 = GetSomeNumbers_List_int();
sw.Stop();
//numbers1 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
var numbers2 = GetSomeNumbers_Array();
sw.Stop();
//numbers2 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
var numbers3 = GetSomeNumbers_Enumerable_Range();
sw.Stop();
//numbers3 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}
и я получил OutOfMemoryException
в методе GetSomeNumbers_Enumerable_Range единственным обходным решением является освобождение памяти с помощью:
numbers = null;
GC.Collect();
Взгляните на эту статью Рико Мариани. Он дает два правила, когда вызывать GC.Collect (правило 1: "Не надо"):
Вам следует избегать использования GC.Collect(), так как он очень дорогой. Вот пример:
public void ClearFrame(ulong timeStamp)
{
if (RecordSet.Count <= 0) return;
if (Limit == false)
{
var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
if (seconds <= _preFramesTime) return;
Limit = true;
do
{
RecordSet.Remove(RecordSet[0]);
} while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
}
else
{
RecordSet.Remove(RecordSet[0]);
}
GC.Collect(); // AVOID
}
РЕЗУЛЬТАТ ИСПЫТАНИЯ: ИСПОЛЬЗОВАНИЕ ЦП 12%
Когда вы переходите на это:
public void ClearFrame(ulong timeStamp)
{
if (RecordSet.Count <= 0) return;
if (Limit == false)
{
var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
if (seconds <= _preFramesTime) return;
Limit = true;
do
{
RecordSet[0].Dispose(); // Bitmap destroyed!
RecordSet.Remove(RecordSet[0]);
} while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
}
else
{
RecordSet[0].Dispose(); // Bitmap destroyed!
RecordSet.Remove(RecordSet[0]);
}
//GC.Collect();
}
РЕЗУЛЬТАТ ТЕСТА: ИСПОЛЬЗОВАНИЕ ЦП 2-3%
Одно полезное место для вызова GC.Collect() - это модульный тест, когда вы хотите убедиться, что вы не создаете утечку памяти (например, если вы что-то делаете с WeakReferences или ConditionalWeakTable, динамически генерируемым кодом и т. Д.).
Например, у меня есть несколько тестов, таких как:
WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);
Можно утверждать, что использование WeakReferences само по себе является проблемой, но кажется, что если вы создаете систему, основанную на таком поведении, то вызов GC.Collect() является хорошим способом проверки такого кода.
Запись Скотта Холдена в блоге о том, когда (а когда нет) вызывать GC.Collect, относится к .NET Compact Framework, но правила, как правило, применяются ко всем управляемым разработкам.
В вашем примере, я думаю, что вызов GC.Collect не проблема, а скорее проблема дизайна.
Если вы собираетесь просыпаться с интервалами (заданными временами), то ваша программа должна быть создана для одного выполнения (выполнить задачу один раз), а затем завершиться. Затем вы настраиваете программу как запланированное задание для запуска через запланированные интервалы.
Таким образом, вам не нужно беспокоиться о вызове GC.Collect (что вы должны делать редко, если вообще когда-либо).
При этом у Рико Мариани есть отличный пост в блоге на эту тему, который можно найти здесь:
Есть ситуации, когда это лучше, чем потом сожалеть.
Здесь одна ситуация.
Можно написать неуправляемую DLL в C#, используя переписывание IL (потому что есть ситуации, когда это необходимо).
Теперь предположим, например, что DLL создает массив байтов на уровне класса - потому что многие из экспортируемых функций нуждаются в таком доступе. Что происходит, когда DLL выгружается? В этот момент автоматически вызывается сборщик мусора? Я не знаю, но, будучи неуправляемой DLL, вполне возможно, что GC не вызывается. И это было бы большой проблемой, если бы его не называли. Когда DLL выгружается, сборщик мусора тоже будет - так кто же будет отвечать за сбор любого возможного мусора и как они это сделают? Лучше использовать сборщик мусора в C#. Иметь функцию очистки (доступную для клиента DLL), в которой переменные уровня класса имеют значение null и вызывается сборщик мусора.
Береженого Бог бережет.
Если вы создаете много нового System.Drawing.Bitmap
объекты, сборщик мусора не очищает их. В конце концов GDI+ подумает, что вам не хватает памяти, и выдаст исключение "Параметр недействителен". ВызовGC.Collect()
время от времени (не слишком часто!) эта проблема решается.
Другая причина заключается в том, что у вас открыт SerialPort на USB-COM-порту, а затем USB-устройство отключено. Поскольку SerialPort был открыт, ресурс содержит ссылку на ранее подключенный порт в системном реестре. В этом случае системный реестр будет содержать устаревшие данные, поэтому список доступных портов будет неправильным. Поэтому порт должен быть закрыт.
Вызов SerialPort.Close() для порта вызывает Dispose() для объекта, но он остается в памяти до фактического запуска сборки мусора, в результате чего реестр остается устаревшим до тех пор, пока сборщик мусора не решит освободить ресурс.
try
{
if (port != null)
port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
port = null;
using(var stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Png);
techObject.Last().Image = Image.FromStream(stream);
bitmap.Dispose();
// Without this code, I had an OutOfMemory exception.
GC.Collect();
GC.WaitForPendingFinalizers();
//
}
Я все еще не уверен в этом. Я работаю с 7 лет на сервере приложений. Наши большие установки используют оперативную память объемом 24 ГБ. Его многопоточный и ВСЕ вызовы для GC.Collect() столкнулись с действительно ужасными проблемами производительности.
Многие Сторонние Компоненты использовали GC.Collect(), когда они думали, что это было разумно сделать прямо сейчас. Таким образом, простая группа Excel-отчетов блокировала сервер приложений для всех потоков несколько раз в минуту.
Нам пришлось провести рефакторинг всех сторонних компонентов, чтобы удалить вызовы GC.Collect(), и после этого все работало нормально.
Но я также запускаю Серверы на Win32, и здесь я начал интенсивно использовать GC.Collect() после получения исключения OutOfMemoryException.
Но я также довольно неуверен в этом, потому что я часто замечал, что когда я получаю OOM на 32-битной системе, и я снова пытаюсь запустить ту же операцию, не вызывая GC.Collect(), она просто работает нормально.
Одна вещь, которая меня интересует, это само исключение OOM... Если бы я написал.Net Framework и не смог бы выделить блок памяти, я бы использовал GC.Collect(), дефрагментировал память (??), попробуйте снова, и если я все еще не могу найти свободный блок памяти, то я бы выбросил OOM-Exception.
Или, по крайней мере, сделайте это поведение настраиваемым параметром из-за недостатков производительности, связанных с GC.Collect.
Теперь в моем приложении много такого кода, чтобы "решить" проблему:
public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{
int oomCounter = 0;
int maxOOMRetries = 10;
do
{
try
{
return func(a1, a2);
}
catch (OutOfMemoryException)
{
oomCounter++;
if (maxOOMRetries > 10)
{
throw;
}
else
{
Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
GC.Collect();
}
}
} while (oomCounter < maxOOMRetries);
// never gets hitted.
return default(TResult);
}
(Обратите внимание, что поведение Thread.Sleep() действительно специфично для приложения, потому что мы запускаем службу кэширования ORM, и службе требуется некоторое время для освобождения всех кэшированных объектов, если объем оперативной памяти превышает некоторые предопределенные значения. несколько секунд в первый раз, и увеличил время ожидания при каждом появлении OOM.)
Одна хорошая причина для вызова GC - на маленьких компьютерах ARM с небольшим объемом памяти, таких как Raspberry PI (работает с моно). Если нераспределенные фрагменты памяти используют слишком много системной памяти, ОС Linux может работать нестабильно. У меня есть приложение, где я должен вызывать GC каждую секунду (!), Чтобы избавиться от проблем переполнения памяти.
Другим хорошим решением является удаление объектов, когда они больше не нужны. К сожалению, во многих случаях это не так просто.
Так как есть куча малых объектов (SOH) и куча больших объектов (LOH)
Мы можем вызвать GC.Collect(), чтобы очистить объект отмены ссылок в SOP и переместить живой объект в следующее поколение.
В.net4.5 мы также можем сжать LOH, используя большой режим сравнения объектов
Это не имеет отношения к вопросу, но для XSLT-преобразований в.NET (XSLCompiledTranform) у вас может не быть выбора. Другим кандидатом является контроль MSHTML.
Если вы используете версию.net меньше 4.5, сбор вручную может быть неизбежен (особенно если вы имеете дело со многими "большими объектами").
эта ссылка описывает почему:
https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/