Это плохая практика, чтобы зависеть от.NET автоматического сборщика мусора?
Можно создать много объектов, интенсивно использующих память, а затем отказаться от ссылок на них. Например, я могу захотеть загрузить и обработать некоторые данные из базы данных, и я сделаю 100 отдельных итераций загрузки и обработки. Я мог бы объявить переменную DataTable один раз, и для каждого запроса сбрасывать ее в новый объект DataTable, используя конструктор, оставляя старый объект DataTable в памяти.
Класс DataTable имеет простые встроенные способы освобождения используемой памяти, включая Rows.Clear() и.Dispose(). Таким образом, я мог сделать это в конце каждой итерации перед установкой переменной в новый объект DataTable. ИЛИ я мог бы забыть об этом и просто позволить сборщику мусора CLR сделать это для меня. Сборщик мусора кажется довольно эффективным, поэтому конечный результат должен быть одинаковым в любом случае. "Лучше" явно избавляться от объектов, интенсивно работающих с памятью, когда они вам не нужны (но для этого нужно добавить код) или просто зависеть от сборщика мусора, который сделает всю работу за вас (вы находитесь во власти алгоритм GC, но ваш код меньше)?
По запросу приведен код, иллюстрирующий пример переработанной переменной DataTable:
// queryList is list of 100 SELECT queries generated somewhere else.
// Each of them returns a million rows with 10 columns.
List<string> queryList = GetQueries(@"\\someserver\bunch-o-queries.txt");
DataTable workingTable;
using (OdbcConnection con = new OdbcConnection("a connection string")) {
using (OdbcDataAdapter adpt = new OdbcDataAdapter("", con)) {
foreach (string sql in queryList) {
workingTable = new DataTable(); // A new table is created. Previous one is abandoned
adpt.SelectCommand.CommandText = sql;
adpt.Fill(workingTable);
CalcRankingInfo(workingTable);
PushResultsToAnotherDatabase(workingTable);
// Here I could call workingTable.Dispose() or workingTable.Rows.Clear()
// or I could do nothing and hope the garbage collector cleans up my
// enormous DataTable automatically.
}
}
}
3 ответа
Хорошо, пришло время немного прояснить ситуацию (так как мой оригинальный пост был немного грязным).
IDisposable не имеет ничего общего с управлением памятью. IDisposable
позволяет объекту очистить любые собственные ресурсы, которые он может удерживать. Если объект реализует IDisposable
, вы должны быть уверены, что либо использовать using
заблокировать или позвонить Dispose()
когда вы закончите с этим.
Что касается определения объектов, интенсивно использующих память, а затем потери ссылок на них, то, как работает сборщик мусора. Это хорошо. Пусть это произойдет, и пусть сборщик мусора сделает свою работу.
... так что, чтобы ответить на ваш вопрос, нет. Нет ничего плохого в том, чтобы зависеть от.NET Garbage Collector. Наоборот, на самом деле.
@Justin
... так что, чтобы ответить на ваш вопрос, нет. Нет ничего плохого в том, чтобы зависеть от.NET Garbage Collector. Наоборот, на самом деле.
Это ужасная практика - полагаться на сборщик мусора для очистки. К сожалению, вы рекомендуете это. Это может привести к утечке памяти, и да, в.NET есть как минимум 22 способа утечки памяти. Я работал с огромным количеством клиентов, которые диагностировали как утечки управляемой, так и неуправляемой памяти, предлагая решения для них, а также рассказывали нескольким группам пользователей.NET о Advanced GC Internals и о том, как работает управление памятью изнутри GC и CLR.
@OP: Вы должны вызвать Dispose() для DataTable и явно установить его равным нулю в конце цикла. Это явно говорит GC, что вы закончили с этим, и больше нет корневых ссылок на него. DataTable помещается на LOH из-за его большого размера. Если вы этого не сделаете, вы можете легко фрагментировать ваш LOH, что приведет к исключению OutOfMemoryException Помните, что LOH никогда не уплотняется!
Для получения дополнительной информации, пожалуйста, обратитесь к моему ответу на
Что произойдет, если я не вызову Dispose на объекте пера?
@Henk - существует связь между IDisposable и управлением памятью; IDisposable допускает полуявное освобождение ресурсов (если реализовано правильно). И ресурсы всегда имеют какую-то управляемую и обычно неуправляемую память, связанную с ними.
Несколько вещей, чтобы отметить о Dispose() и IDisposable здесь:
IDisposable обеспечивает удаление как управляемой, так и неуправляемой памяти. Утилизация неуправляемой памяти должна выполняться в методе Dispose, и вы должны предоставить финализатор для вашей реализации IDisposable.
GC не вызывает Dispose для вас.
Если вы не вызываете Dispose(), GC отправляет его в очередь Finalization и в конечном итоге снова в очередь f-достижимости. Финализация заставляет объект пережить 2 коллекции, что означает, что он будет повышен до Gen1, если он был в Gen0, и до Gen2, если он был в Gen1. В вашем случае объект находится на LOH, поэтому он сохраняется до тех пор, пока полный GC (все поколения плюс LOH) не будет выполнен дважды, что в "здоровом".NET-приложении один полный сбор выполняется приблизительно. 1 на каждые 100 коллекций. Так как в зависимости от вашей реализации на кучу LOH и GC оказывается большое давление, полные GC будут срабатывать чаще. Это нежелательно по соображениям производительности, поскольку полный сборщик мусора занимает гораздо больше времени. Кроме того, существует зависимость от того, какой тип GC вы используете, и используете ли вы LatencyModes (будьте очень осторожны с этим). Даже если вы используете Background GC (он заменил Concurrent GC в CLR 4.0), эфемерная коллекция (Gen0 и Gen1) по-прежнему блокирует / приостанавливает потоки. Это означает, что никакие распределения не могут быть выполнены в течение этого времени. Вы можете использовать PerfMon для мониторинга поведения использования памяти и активности GC в вашем приложении. Обратите внимание, что счетчики GC обновляются только после проведения GC. Для получения дополнительной информации о версиях GC см. Мой ответ на
Dispose() немедленно освобождает ресурсы, связанные с вашим объектом. Да, GC недетерминирован, но вызов Dispose() не вызывает GC!
Dispose() сообщает GC, что вы закончили с этим объектом, и его память может быть возвращена в следующей коллекции для поколения, в котором этот объект живет. Если объект живет в Gen2 или на LOH, эта память не будет восстановлена, если будет иметь место коллекция Gen0 или Gen1!
Финализатор работает в 1 потоке (независимо от используемой версии GC и количества логических процессоров на машине. Если вы много занимаетесь в очередях Финализация и f-достижимы, у вас есть только 1 поток, обрабатывающий все готовое для Финализации; ваше выступление идет, вы знаете, где...
Информацию о том, как правильно реализовать IDisposable, смотрите в моем блоге:
Я также согласен с постом Дэйва. Вы всегда должны утилизировать и освобождать соединения с базой данных, даже если в вашей среде есть документация, которая вам не нужна.
Как администратор БД, работавший с MS SQL, Oracle, Sybase/SAP и MYSQL, меня привлекли к работе над таинственной блокировкой и утечкой памяти, которая была обвинена в базе данных, хотя на самом деле проблема заключалась в том, что разработчик не закрыть и уничтожить их объекты связи, когда они были сделаны с ними. Я даже видел приложения, которые оставляли незанятые соединения открытыми в течение нескольких дней, и они действительно могут сделать что-то плохое, когда ваша база данных кластеризована, зеркально отображена и всегда в группах восстановления в SQL Server 2012.
Когда я взял свой первый класс.Net, инструктор научил нас держать открытыми соединения с базой данных, пока вы их используете. Садись, делай свою работу и уходи. Это изменение сделало несколько систем, которые я помогаю оптимизировать, намного более надежными. Это также освобождает память подключения в СУБД, давая больше оперативной памяти для буферизации ввода-вывода.