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

Jos*_*nig 5 .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.
            }   
        }
    }
Run Code Online (Sandbox Code Playgroud)

Dav*_*ack 7

@Justin

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

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

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

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

如果我不在笔对象上调用Dispose会发生什么?

@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 Heap和GC存在很大的压力,根据您的实施情况,完整的GC会更频繁地发射.出于性能原因,这是不合需要的,因为完整的GC需要更长的时间才能完成.然后还有一个依赖于你正在运行什么样的GC以及你是否正在使用LatencyModes(要非常小心).即使你正在运行Background GC(这已经取代了CLR 4.0中的Concurrent GC),短暂集合(Gen0和Gen1)仍然会阻塞/挂起线程.这意味着在此期间无法执行任何分配.您可以使用PerfMon监视应用程序上的内存利用率和GC活动的行为.请注意,GC计数器在GC发生后才会更新.有关GC版本的其他信息,请参阅我的回复

    确定正在运行的垃圾收集器.

  4. Dispose()会立即释放与您的对象关联的资源.是的,GC具有不确定性,但调用Dispose()并不会触发GC!

  5. Dispose()让GC知道您已完成此对象,并且可以在下一个集合中回收它的内存,以用于该对象所在的生成.如果对象位于Gen2或LOH中,如果发生Gen0或Gen1集合,则不会回收该内存!

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

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

你如何正确实现IDisposable模式?

  • "Dispose()让GC知道".GC无视调用`Dispose`. (2认同)

Jus*_*ner 5

好的,该花点时间整理一下了(因为我的原始帖子有点泥泞)。

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

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

...因此,要回答您的问题,不。依靠.NET垃圾收集器并不是一个坏习惯。实际上恰恰相反。