【问题标题】:Is it bad practice to depend on the .NET automated garbage collector?依赖 .NET 自动垃圾收集器是不好的做法吗?
【发布时间】:2011-11-23 04:01:41
【问题描述】:

可以创建大量内存密集型对象,然后放弃对它们的引用。例如,我可能想从数据库中下载和操作一些数据,我将进行 100 次单独的下载和处理迭代。我可以声明一个 DataTable 变量,然后为每个查询使用构造函数将其重置为新的 DataTable 对象,从而放弃内存中的旧 DataTable 对象。

DataTable 类具有简单的内置方法来释放它使用的内存,包括 Rows.Clear() 和 .Dispose()。因此,在将变量设置为新的 DataTable 对象之前,我可以在每次迭代结束时执行此操作。或者我可以忘记它,让 CLR 垃圾收集器为我做这件事。垃圾收集器似乎非常有效,因此最终结果应该是相同的。当您不需要它们时,显式处理大量内存对象是“更好”吗(但添加代码来执行此操作)还是仅仅依靠垃圾收集器为您完成所有工作(您受GC算法,但你的代码更小)?

根据要求,这里是说明回收数据表变量示例的代码:

    // 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.
            }   
        }
    }

【问题讨论】:

  • 您的问题(标题中的那个)的答案是NO。显示您的代码以获得更详细的答案。如果你不依赖 .NET GC,那你肯定做错了什么。
  • 这个问题取决于“明确处理大量内存对象”。这在 .NET 中是不可能的,只有垃圾收集器才能做到这一点。 IDisposable 与内存无关。
  • @Hans Passant,为IDisposable has nothing to do with memory+1。
  • @jmh_gr,如果没有具体的例子,我们就无法具有建设性。
  • IDisposable 有助于尽早释放非托管资源。内存是一种不受管理的资源。

标签: .net garbage-collection


【解决方案1】:

@贾斯汀

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

依靠 GC 为您清理是一种可怕的做法。不幸的是,您建议这样做。这样做很可能会导致您走上内存泄漏的道路,是的,在 .NET 中至少有 22 种方法可以“泄漏内存”。我曾在大量客户中工作,诊断托管和非托管内存泄漏,为他们提供解决方案,并在多个 .NET 用户组上介绍了 Advanced GC Internals 以及内存管理如何从 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 实现提供终结器。

  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(对此要非常小心)。即使 您正在运行后台 GC(这已取代 CLR 中的并发 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 个线程处理一切准备好完成;你的表现会去你知道的地方......

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

How do you properly implement the IDisposable pattern?

【讨论】:

  • “Dispose() 让 GC 知道”。 GC 忽略了对 Dispose 的调用。
  • 好的,我应该澄清一下:Dispose() 不会“调用”GC,而 GC 从不调用 Dispose()。我的意思是,当 Dispose() 被调用时,GC 对一组内存进行操作,该内存已被置于“非引用”状态 - 即删除根引用并防止对象提升到下一代 GC 中。换句话说,调用 Dispose() 与 GC 要做的工作有直接的关系。
  • “GC 在一组已进入“非引用”状态的内存上运行 - 即删除根引用并阻止对象提升到下一代 GC”。您是在谈论具有明确删除引用的Dispose 实现的类还是任何Disposable 的一般情况? AFAIK,GC 忽略了对 Dispose 的调用,因此调用 Dispose 通常不会对升级到下一代产生任何影响(这完全取决于可达性)。
  • 我说的是 GC 是如何工作的,与 IDisposable 无关。 GC 对引用类型的托管对象(通常放置在堆上)进行操作 - 无论它们是否实现 IDisposable。当需要清理资源(最终释放与资源关联的内存)时,一个类将/应该实现 IDisposable。回复如下...
  • “Dispose() 可以对提升和可达性产生影响”。是的。所有代码都会对促销和可达性产生影响。您描述的特征适用于所有代码,与Dispose 无关。
【解决方案2】:

好的,是时候把事情弄清楚了(因为我原来的帖子有点泥泞)。

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

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

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

【讨论】:

  • 很遗憾你继续 Dispose() 和内存管理之间的链接。 IDisposable 是关于资源的,仅在非常罕见(且有问题)的情况下与内存有关。
  • @Henk - 已经编辑了回复,提到 Dispose() 与托管内存管理无关)。
  • 也许我在这里只关注语义,但 IDisposable (尽管间接)与内存管理有关。是的,IDisposable 的真正目的是主动释放资源,这样您就不会对 GC 造成压力。但是,我无法想到(不由自主地)对应用程序内存消耗/利用率没有影响的单个资源。用 WinDbg 做一些调试,你就会明白我指的是什么。
【解决方案3】:

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

作为一名使用过 MS SQL、Oracle、Sybase/SAP 和 MYSQL 的 DBA,我被请来研究被归咎于数据库的神秘锁定和内存泄漏问题,而事实上,问题是因为开发人员在完成连接对象后没有关闭和销毁它们。我什至见过一些应用程序让空闲连接打开数天,当你的数据库被集群化、镜像并且在 SQL Server 2012 中使用 Always on 恢复组时,它真的会让事情变得很糟糕。

当我上第一堂 .Net 课程时,讲师教我们只在使用数据库连接时保持打开状态。进去,完成你的工作,然后出去。这一变化使我帮助优化的几个系统更加可靠。它还释放 RDBMS 中的连接内存,为缓冲区 IO 提供更多内存。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-10
    • 1970-01-01
    • 2012-04-09
    • 2010-10-04
    • 1970-01-01
    相关资源
    最近更新 更多