【发布时间】:2013-03-05 00:55:30
【问题描述】:
前几天,当我意识到我对清理资源几乎一无所知时,我正在尝试消除应用程序中的一些内存泄漏的方法。我做了一些研究,希望只调用 .dispose() 就能解决我所有的问题。我们的数据库中有一个表,其中包含大约 65,000 条记录。显然,当我从数据适配器填充数据集时,内存使用率会变得非常高。当我在数据集上调用 dispose 方法时,我惊讶地发现没有释放内存。为什么会这样?清除数据集也无济于事。
【问题讨论】:
前几天,当我意识到我对清理资源几乎一无所知时,我正在尝试消除应用程序中的一些内存泄漏的方法。我做了一些研究,希望只调用 .dispose() 就能解决我所有的问题。我们的数据库中有一个表,其中包含大约 65,000 条记录。显然,当我从数据适配器填充数据集时,内存使用率会变得非常高。当我在数据集上调用 dispose 方法时,我惊讶地发现没有释放内存。为什么会这样?清除数据集也无济于事。
【问题讨论】:
IDisposable 因此Dispose 不用于减少内存压力,尽管在某些情况下它可能会,而是用于确定性清理。
考虑到这一点,您构建了一个对象,该对象维护与您的数据库服务器的活动且开放的连接。此连接使用您的计算机和服务器上的资源。
您当然可以在使用完对象后将其保留,最终它会被垃圾收集器拾取,但假设您想确保至少资源被释放,因此连接关闭,当你完成它。这就是IDisposable.Dispose 发挥作用的地方。
用于清理对象管理的资源。
但是,它不会释放分配给对象的托管内存。这仍然留给垃圾收集器,稍后会开始执行此操作。
您是否真的有内存问题,或者您只是在任务管理器或类似工具中查看内存使用情况并说“这有点高。”?
如果是后者,那么您应该暂时保留它。如果您的可用内存较少,.NET 将更频繁地运行垃圾收集,因此除非您遇到或可能怀疑您很快就会遇到内存溢出情况,否则您可能不会遇到任何问题.
让我解释一下“少跑”的意思。
如果您的计算机中有 8GB 内存,并且只运行 Windows 和记事本,那么大部分内存都将可用。当你现在运行你的程序时,即使它加载了小数据块到内存中,你也可以一直这样做,并且内存使用量会稳步增长。我不知道 GC 究竟什么时候会启动并尝试减少您的内存占用,但我几乎可以向您保证,您会想知道为什么它会变得如此之高。
为了论证的缘故,我们假设您的程序最终将使用 2GB 内存。
现在,如果您在可用内存较少的机器上运行程序,GC 会更频繁地发生,并且会以较低的限制启动,这可能会使内存使用量低于 500MB 甚至可能更少。
这里要注意的重要部分是,为了让您准确了解应用程序实际需要多少内存,您不能依赖任务管理器或类似方法来衡量它,你需要更有针对性的东西。
【讨论】:
调用 Dispose() 只会释放非托管资源,例如文件句柄、数据库连接、非托管内存等。它不会释放垃圾回收的内存。
垃圾收集的内存只会在下一次收集时释放。通常在应用程序域内存满时。
【讨论】:
我将在此指出一些没有明确提及的内容:调用Dispose() 只会在组件的开发人员对其进行编码的情况下清理(免费)非托管资源。
我的意思是:如果您怀疑自己有内存泄漏,调用 Dispose() 并不能解决它如果原始开发人员做得很糟糕并且没有正确释放非托管资源。更多信息,check this blog post。注意声明Dispose 的行为由开发者定义。
【讨论】:
某些对象会要求一个或多个其他实体代表它做某事,直到另行通知,这会损害其他实体。如果这样做的对象在没有通知之前的实体不再需要他们的服务的情况下消失了,这些实体将继续代表不再需要它们的对象进行无用的行动,从而继续损害其他想要的实体使用它们。
在许多情况下,为了让对象“George”告诉外部实体“Joe”不再需要它的服务,George 必须知道不再需要 它的 服务。在 .NET 中可以通过两种正常方式实现,最终化和IDIsposable。
如果一个对象重写了一个名为Finalize 的方法,那么在创建该对象时,.NET 垃圾收集器会将其添加到一个已注册终结器的对象列表中。如果 GC 发现除该列表之外不存在对该对象的根引用,则 GC 将从该列表中删除该对象并将其添加到对象的强根队列中,该对象应立即调用其 Finalize 方法可能的。然后,此类对象可以使用其Finalize 方法通知其他实体不再需要他们的服务。
虽然基于最终确定的清理有时可以奏效,但无法保证及时性。在 .net 的设计过程中,Microsoft 可能曾打算将最终化作为主要的清理方法,但由于各种原因,它不能安全地依赖。
另一个清理方法,应该是一个人努力的重点,是IDisposable。基本上,IDisposable 背后的想法很简单:对于实现IDisposable 的每个对象,都应该有一个实体(通常是一个对象或嵌套的执行范围)负责确保该对象的IDisposable.Dispose 方法将得到在 Universe 的生命周期内的某个时间调用(这意味着在对对象的引用仍然存在时的某个时间),并且最好在代码可以告诉您不再需要该对象的服务时立即调用。
请注意,IDisposable.Dispose 通常承诺任何被要求代表对象做某事的外部实体将被告知他们不再需要这样做,但这样的承诺并不意味着实体的数量是非零。如果一个对象没有要求任何外部实体代表它做任何事情,那么传递一个消息“所有”这些实体根本不需要做任何事情。另一方面,Dispose 方法在某些情况下可能什么都不做的事实并不意味着它保证在任何情况下都不会做任何事情,也不意味着在它会做某事的情况下未能调用它不会不会产生不利影响。
【讨论】: