【问题标题】:ASP.NET Website Memory Usage quite highASP.NET 网站内存使用率相当高
【发布时间】:2011-03-25 05:59:09
【问题描述】:

我有一个 ASP.NET 网站,它将在大约 3-4 天内使用大约 2gb 的物理内存,这对我来说听起来很糟糕。目前,我已将 IIS 配置为在达到 500mb 时重新启动应用程序池进程。我想尝试找出问题所在。

在 .NET 中创建对象的新实例时,我的印象是不需要释放它,因为 .NET 垃圾收集器会为我做这件事。

是这种情况还是这可能是我遇到问题的原因之一?

【问题讨论】:

  • 服务器为其会话超时参数配置了什么?
  • 我看了看,可以看到我网站的会话设置为 In Proccess 并且对 asp cookie 有超时,但我找不到专门用于会话的会话。你能建议吗?
  • 这是我的 web.config 文件中用于会话的行: - 似乎设置为 20 分钟。

标签: .net asp.net memory-management memory-leaks


【解决方案1】:

您的高内存使用可能有很多原因,但 .NET 中的垃圾收集是一件非常精确的事情。也就是说,它对您有很多作用,但有时不如您预期的那么多。

具体来说,它只能清理没有活动引用的对象,所以如果你完成了一个类,但仍然有对它的引用,你需要删除该引用,以便 GC 可以为你恢复那段记忆。此外,如果您有任何不活动的打开连接(例如,到数据库或其他东西),请不要忘记关闭并处理它们。通常,我们将此类对象包装在 using 块中,如下所示:

using(SqlConnection conn = new SqlConnection(myConnString))
{ ...rest of code here }

这将自动关闭并释放连接。这是作为 try...finally 块实现的,因此即使在 using 块中抛出异常也会关闭连接。

除此之外,答案是“配置文件、配置文件、配置文件”,直到您找到您的泄漏/瓶颈/其他。

【讨论】:

  • 是的,使用using 语句等效于使用try-finally 块并在Dispose 块中调用Dispose
【解决方案2】:

虽然您确实不必显式释放内存并且垃圾收集器会为您执行此操作,但代码可能会无意中持有对对象的引用以防止它被收集 - 这本质上是一个内存泄漏。

在 ASP.NET 中,对象在被应用程序和会话缓存引用时保持“活动”。在不了解您的应用程序的情况下,我建议您使用内存分析器并更仔细地查看内存中的确切内容以及应用程序或会话缓存是否保留了不应该保留的内容。

【讨论】:

    【解决方案3】:

    .NET 中的内存泄漏仍然存在。确实,在大多数情况下您不需要释放对象(有一些例外,例如 Graphics 对象),但如果您保留对它们的引用,它们将不会被垃圾收集,因为它们仍然被引用。

    如果 GC 发现某个对象在您的应用程序中的某个位置被引用,它不会丢弃它。

    查看这篇关于 .NET 内存泄漏以及如何定位和修复它们的 Code Project 文章。

    【讨论】:

      【解决方案4】:

      .NET 将非常有效地为您管理垃圾收集。虽然在实现IDisposable 的类型上调用Dispose 方法是明智的,但这可能不是您的问题。 .NET 中可能发生内存泄漏的原因有很多。以下是一些:

      • 您在 Session 中为每个用户缓存了太多数据。
      • 您在应用程序缓存或字典等静态变量中的应用程序级别缓存了太多数据。
      • 您将 Web 控件(或用户控件)存储在会话或应用程序级别。
      • 您可以将实例与静态类型上的事件或不断被引用的类型挂钩(因为它们存储在缓存中)。

      我希望这能给你一些关于在哪里看的想法。

      更新:你应该关注this video 了解 ASP.NET 调试。

      更新 2: 关于您对我的回答的评论如下。 CLR 将收集所有托管内存,因此您使用new 创建的所有对象都将被收集。从这个意义上说,对象是否实现IDisposable 并不重要。然而,很多时候您需要直接或间接地使用本机资源(例如文件句柄、图形句柄、数据库连接、使用本机 - 因此非托管内存)。 CLR 不知道如何释放这些资源。为此,.NET 具有终结器的概念。终结器是类的开发人员可以实现的虚拟方法。执行此操作时,CLR 将在该类型的实例未被引用且被收集之前调用此方法。终结器通常包含释放这些资源的逻辑。换句话说,当一个类型需要原生资源时,它通常会有一个终结器方法允许该类型释放这些资源。

      关于 CLR 的事情,故事到此结束。 CLR 没有对实现IDisposable 接口的对象的特定处理。然而,.NET 垃圾收集器本质上是不确定的。这意味着您不知道它何时运行以及是否运行。这意味着在您的原生资源被清理之前可能需要很长时间(因为终结器只会在收集后被调用)。然而,对于许多资源,有必要在使用完它们后立即释放它们。例如,当您不关闭数据库连接或通过 System.Drawing 命名空间在 .NET 中使用 GDI+ 时,您往往会很快用完数据库连接。

      因此引入了IDisposable 接口。同样,CLR 和垃圾收集器不会查看此接口。它是类型与其用户之间的契约,允许其用户直接释放对象的底层资源。在正常设计中,对象的终结器和对象的 Dispose 方法都将调用相同的私有或受保护方法,以释放这些资源。当一个类型实现 IDisposable 时,明智的做法是在完成后调用它的 Dispose 方法,或者将对象包装在 using 语句中,以允许确定性地释放本机资源。

      所以回到你的问题。 GC 将收集所有托管对象,但本机资源不会。因此,类型可能会实现终结器方法,并且这些对象通常也会实现 IDisposable 接口。对它们调用Dispose 将显式直接释放这些原生资源。

      我希望这是有道理的。

      【讨论】:

      • 这是否意味着只有继承自 IDisposable 的类才会被 GC?
      • 我认为您可能刚刚找到了我的问题的原因 :) 我网站上的所有图像都是通过我编写的自定义处理程序运行的,该处理程序会创建该图像的缩略图。据我所知,我没有对该代码做任何清理工作,它肯定使用了 system.drawing 方法。对我来说是个好地方。谢谢你的帖子。已将此标记为答案,因为它为我提供了目前所需的最多信息。
      • 如果您有疑问,您可以随时在 SO 此处发布新问题并显示相关代码。祝你好运。
      • 谢谢 Steven,我已经发布了另一个问题:stackoverflow.com/questions/3415693/… 只是为了澄清关于要处理的内容。
      • 对我来说,一直与 ASP.NET 应用程序中的内存泄漏相关的一种类型是 XmlSerializer,尽管它确实实现了 IDisposable,但它在内存中创建了从未处理过的新程序集(一旦程序集加载到进程中,它就会一直留在那里直到进程终止)。
      【解决方案5】:

      有几件事你应该看看:

      首先,您是否使用会话?他们在 proc 或 SQL 会话中吗?如果它们正在处理中,设置的超时时间是多少?如果您有一个非常长的超时,这可以解释为什么您使用这么多内存(用户会话将被存储很长时间)。

      第二,处理对象。 .NET 垃圾收集器将为您摆脱引用,但是当您创建实现 IDisposable 接口的对象时,您应该始终使用 using 关键字。

      using(Foo foo = new Foo())
      {
          ...
      }
      

      相当于做:

      Foo foo;
      try
      {
          foo = new Foo();
          ...
      }
      finally
      {
          foo.Dispose();
      }
      

      这将确保您有效地处理您的物品。

      如果您仍然无法在代码中找到任何明显的内容,您可以对其进行分析,从调用次数最多的方法开始。您可以找到有关优秀分析器的信息here。这肯定会导致您找到问题的根源。

      【讨论】:

        【解决方案6】:

        如果您的 ASP.NET 应用程序使用事件(哪个不使用),then you have to make sure you unsubscribe from the event

        经验法则:

        如果你使用+=订阅一个事件,那么你需要在Dispose()方法中使用-=来取消订阅。

        lots of resources [关于这个主题][5],我在我工作过的应用程序中遇到了一些痛苦,因为你的普通程序员没有'没有意识到事件会导致内存泄漏。

        [5]: https://www.google.com/?gws_rd=ssl#hl=en&source=hp&q=.NET 事件内存泄漏&aq=f&aqi=g1&aql=&oq=&gs_rfai=CnCywlWZZTMSQDYL-jQOA1ozyCAAAAKoEBU_Q8wsE&fp=ea2cd8eab02d18af

        【讨论】:

        • 在大多数情况下,取消订阅是不必要的。仅当您订阅长寿命对象上的事件时才需要它。单个页面上的控件等短期对象之间的事件不需要手动清理:stackoverflow.com/questions/345130/…
        【解决方案7】:

        当您的内存超过 500 MB 时,请使用 memory profiler(在内存转储或实时环境中)以了解哪些对象占用了所有空间。

        在您的开发环境中进行分析时,您可能会看到一种特定的对象类型仅在您的网站上浏览时才会增长,而其他对象类型在全球范围内保持不变。

        【讨论】:

          猜你喜欢
          • 2015-04-02
          • 1970-01-01
          • 1970-01-01
          • 2012-05-31
          • 1970-01-01
          • 2011-03-31
          • 1970-01-01
          • 2013-10-27
          • 2011-09-19
          相关资源
          最近更新 更多