4

我在一次采访中被问到这个问题:众所周知,C# 中的内存泄漏问题是如何发生的,因为垃圾收集器负责所有与内存管理相关的工作?那怎么可能呢?

4

5 回答 5

2

来自MSDN:-

当在程序中分配内存并且永远不会返回给操作系统时,即使程序不再使用内存,也会发生内存泄漏。以下是四种基本类型的内存泄漏:

  • 在手动管理的内存环境中:内存是由指针动态分配和引用的。指针在内存被释放之前被擦除。指针被擦除后,内存不能再被访问,因此不能被释放。
  • 在动态管理的内存环境中:内存被释放但从未被收集,因为对对象的引用仍然处于活动状态。因为对该对象的引用仍然处于活动状态,所以垃圾收集器永远不会收集该内存。这可能发生在系统或程序设置的引用中。
  • 在动态管理的内存环境中:垃圾收集器可以收集和释放内存,但永远不会将其返回给操作系统。当垃圾收集器无法将仍在使用的对象移动到内存的一部分并释放其余部分时,就会发生这种情况。
  • 在任何内存环境中:当声明许多大对象并且不允许离开范围时,可能会导致内存管理不佳。结果,内存被使用并且从未被释放。
 Dim DS As DataSet
  Dim cn As New SqlClient.SqlConnection("data source=localhost;initial catalog=Northwind;integrated security=SSPI")
  cn.Open()

  Dim da As New SqlClient.SqlDataAdapter("Select * from Employees", cn)
  Dim i As Integer
  DS = New DataSet()

  For i = 0 To 1000
      da.Fill(DS, "Table" + i.ToString)
  Next

尽管此代码显然效率低下且不实用,但它旨在演示如果将对象添加到集合中(例如将表添加到 DataSet 集合中),只要集合保持活动状态,对象就会保持活动状态。如果在程序的全局级别声明了一个集合,并且在整个程序中声明了对象并将其添加到该集合中,这意味着即使对象不再在范围内,这些对象仍然存在,因为它们仍然被引用。

您也可以查看此参考:-

上面的链接给出了一个很好的结论

尽管 .NET 减少了您对内存的关注,但您仍然必须注意应用程序对内存的使用,以确保其行为良好且高效。仅仅因为应用程序被管理并不意味着您可以放弃良好的软件工程实践并依靠 GC 来执行魔术。

于 2013-10-27T13:59:12.090 回答
2

当您实际上不再使用对象时,仅保留对对象的引用是一种很好的泄漏方式。特别是当您将其存储在静态变量中时,这会创建一个在 AppDomain 的生命周期中存在的引用,除非您将其显式设置回 null。如果这样的引用存储在集合中,那么您可能会得到真正的泄漏,这可能会使您的程序因 OOM 而崩溃。不常见。

这种泄漏很难找到。特别是事件可能会很棘手,当您订阅事件处理程序时,它们将获取事件源对象以添加对事件处理程序对象的引用。在 C# 代码中很难看到,因为您在订阅事件时从未显式传递它。如果事件源对象存在很长时间,那么您可能会遇到所有订阅者对象都被引用的问题。

棘手的问题确实需要内存分析器来诊断。

于 2013-10-27T14:03:24.230 回答
1

示例Child包含ClickEventHandler订阅ClickEvent另一个类的事件的方法的类Parent

GCChild将被阻止,直到Parent类超出范围..即使Child超出范围,GC 也不会收集,直到Parent超出范围


EventGC 不会收集所有订阅 Broadcaster() 的订阅者,直到广播者超出范围。

所以,它是一种单向关系

Broadcaster(ClickEvent) -> Subscribers(ClickEventHandler)

所有的 GCClickEventHandlers都会被阻塞,直到ClickEvent超出范围!

于 2013-10-27T14:14:23.590 回答
0

例如:

您有主窗体和静态变量 Popups[] collectionOfPopups 的应用程序。将所有应用程序弹出窗口对象存储到静态数组中,并且永远不要删除它们。

因此,每个新的弹出窗口都会占用内存,而 GC 永远不会释放它。

尝试阅读 GC 的工作原理 http://msdn.microsoft.com/en-us/library/ee787088.aspx

这将解释一切。

于 2013-10-27T14:02:10.103 回答
0

可能有多种原因,但这里有一个:

考虑两个类:

class A
{
  private B b;
}

class B
{
  private A a;
}

如果您为这些类中的每一个创建一个对象,并将它们交叉链接,然后这两个对象都超出了范围,您仍然可以在另一个对象中链接到它们中的每一个。GC 很难捕捉到这些类型的交叉链接,它可能会继续认为这两个对象仍在使用中。

于 2013-10-27T14:02:47.420 回答