依赖于.NET自动垃圾收集器是不好的做法吗?

时间:2011-09-22 20:14:59

标签: .net garbage-collection

可以创建大量内存密集型对象,然后放弃对它们的引用。例如,我可能想要从数据库下载和操作一些数据,我将进行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 个答案:

答案 0 :(得分:6)

@Justin

  

...所以,回答你的问题,不。依赖.NET垃圾收集器并不是一个坏习惯。事实恰恰相反。

依靠GC为您进行清理是一种可怕的做法。不幸的是你推荐这个。这样做很可能会导致你陷入内存泄漏的路径,并且是的,至少有22种方法可以在.NET中“泄漏内存”。我曾在大量客户端诊断托管和非托管内存泄漏,为他们提供解决方案,并在Advanced GC Internals上的多个.NET用户组以及内部管理如何在GC和CLR内部工作。

@OP: 您应该在DataTable 上调用Dispose()并在循环结束时将显式设置为null。这明确地告诉GC你完成了它并且没有更多的根对它的引用。由于数据量大,DataTable正被放置在LOH上。不这样做很容易破坏你的LOH导致OutOfMemoryException。记得LOH从未被压缩过!

有关其他详细信息,请参阅我的回答

<强> What happens if I don't call Dispose on the pen object?

@Henk - IDisposable和内存管理之间的关系; IDisposable允许半显式释放资源(如果正确实现)。资源总是有某种托管的通常与它们相关联的非托管内存。

有关Dispose()和IDisposable的几点注意事项:

  1. IDisposable可以处理 托管和非托管内存。非托管内存的处理应该在Dispose方法中完成,您应该为IDisposable实现提供Finalizer。

  2. GC 为您调用Dispose。

  3. 如果您不调用Dispose(),GC会将其发送到Finalization 队列,最终再次到f-reachable队列。定稿 使一个对象存活 2 集合,这意味着它将成为 如果它在Gen0中则提升为Gen1,如果在Gen1中则提升为Gen2。 在你的情况下,对象在LOH上,所以它会存活到一个完整的状态 GC(所有代加LOH)进行两次,其中, 在一个“健康”的.NET应用程序下,一个完整的集合是 表现约每100个系列中就有1个。因为有很多 根据您的实施情况,LOH堆和GC的压力已满 GC会更频繁地发射。这对性能来说是不可取的 完整GC的原因需要更多时间才能完成。然后那里 也依赖于你正在运行什么样的GC以及是否 你正在使用LatencyModes(要非常小心)。即使 你正在运行Background GC(这已经取代了CLR中的Concurrent GC 4.0),短暂集合(Gen0和Gen1)仍然阻塞/暂停线程。这意味着在此期间不能执行任何分配 时间。您可以使用PerfMon来监视内存的行为 应用程序的利用率和GC活动。请注意GC GC发生后,计数器仅更新 。对于 有关GC版本的其他信息,请参阅我对

    的回复

    <强> Determining which garbage collector is running

  4. Dispose()立即发布与您的对象相关联的资源。是的,GC是非确定性的,但调用Dispose()会触发GC!

  5. Dispose()让GC知道你完成了这个对象,它的内存可以在下一个集合中回收,用于生成该对象的那一代。如果对象位于Gen2或LOH中,如果发生Gen0或Gen1集合,则不会回收该内存!

  6. Finalizer在1个线程上运行(无论正在使用的GC版本和计算机上的逻辑处理器数量如何。如果你在Finalization和f-reachable队列中坚持很多,你只有1个线程处理完成所有准备工作;你的表现让你知道在哪里...

  7. 有关如何正确实施IDisposable的信息,请参阅我的博客文章:

    <强> How do you properly implement the IDisposable pattern?

答案 1 :(得分:3)

好的,是时候清理一下了(因为我原来的帖子有点浑浊)。

IDisposable与内存管理无关。 IDisposable允许对象清理它可能持有的任何本机资源。如果对象实现IDisposable,您应确保使用using块或在完成后调用Dispose()

至于定义内存密集型对象,然后丢失对它们的引用,这就是垃圾收集器的工作方式。那是一件好事。让它发生,让垃圾收集器完成它的工作。

...所以,回答你的问题,不。依赖.NET垃圾收集器并不是一个坏习惯。事实恰恰相反。

答案 2 :(得分:1)

我也同意戴夫的帖子。您应该始终处置和释放数据库连接,即使您正在使用的框架具有不需要的文档。

作为一名曾使用过MS SQL,Oracle,Sybase / SAP和MYSQL的DBA,我被引入了神秘的锁定和内存泄漏问题,而实际上,这个问题是因为开发人员完成后不会关闭并销毁他们的连接对象。我甚至看到过几天打开空闲连接的应用程序,在数据库进行集群,镜像和SQL Server 2012中的Always on recovery groups时,它确实会让事情变得糟糕。

当我拿到我的第一个.Net类时,讲师教我们只在你使用它们时保持数据库连接打开。进入,完成工作并离开。这一变化使我帮助优化的几个系统更加可靠。它还释放了RDBMS中的连接内存,为缓冲区IO提供了更多内存。