ASP.NET网站的内存使用率相当高

20

我有一个ASP.NET网站,在大约3-4天内将使用约2GB的物理内存,这对我来说听起来非常糟糕。目前,我已经配置了IIS,当它达到500MB时,重新启动应用程序池进程。我想尝试追踪问题。

在.NET中创建对象的新实例时,我认为不需要释放它,因为.NET垃圾回收器会代替我执行此操作。

这是真的吗?或者这可能是我遇到问题的原因之一吗?


服务器为其会话超时参数配置了什么? - Tim Coker
我查看了一下,发现我的网站会话设置为“正在处理”,并且对于 asp cookie 存在超时,但是我找不到专门针对会话的超时设置。你能给予建议吗? - webnoob
这是我 web.config 文件中关于会话的行:<sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;user id=sa;password=" cookieless="false" timeout="20" /> - 看起来设置为 20 分钟。 - webnoob
7个回答

18

.NET会高效地为您管理垃圾回收。尽管对于实现IDisposable接口的类型,调用Dispose方法是明智的,但这可能不是您的问题。在.NET中,内存泄漏可能有很多原因。以下是一些原因:

  • 您在Session中为每个用户缓存了太多数据。
  • 您在应用程序缓存或静态变量(如字典)中缓存了过多的应用程序级别的数据。
  • 您在Session或应用程序级别中存储Web控件(或用户控件)。
  • 您将实例钩子连接到静态类型的事件或持续引用的类型上(因为它们存储在缓存中)。

我希望这为您提供了一些查找问题的思路。

更新:您应该观看此视频以了解关于ASP.NET调试的更多信息。

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

就CLR而言,这里的故事到此为止了。CLR没有特定的处理实现IDisposable接口的对象的方法。然而,.NET垃圾回收器是不确定性的。这意味着您不知道它何时运行以及是否运行。这意味着它可能需要很长时间才能清理您的本机资源(因为垃圾回收器只会在收集后调用终结器)。然而,对于许多资源,只有在完成时才释放它们是必要的。例如,在通过System.Drawing名称空间使用GDI+时或者当您不关闭数据库连接时,您很快就会用完它们。

出于这个原因,引入了IDisposable接口。再次强调一下,CLR和垃圾回收器不看这个接口。它是类型与其用户之间的契约,允许用户直接释放对象的基础资源。在正常设计中,对象的终结器和对象的Dispose方法都将调用相同的私有或受保护方法来释放这些资源。当类型实现IDisposable时,最好在完成后调用其Dispose方法,或者将对象包装在using语句中以使本机资源的释放具有确定性。

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

希望这有意义。


这是否意味着只有继承自IDisposable的类才会被垃圾回收? - webnoob
我认为你可能刚刚找到了我的问题的原因 :) 我网站上的所有图像都通过我编写的自定义处理程序运行,该处理程序创建该图像的缩略图。据我所记,我没有在那段代码中进行任何清理,它肯定使用了system.drawing方法。这对我来说是一个很好的起点。感谢您的帖子。我已将此标记为答案,因为它为我提供了目前所需的大部分信息。 - webnoob
如果您有疑问,您可以随时在SO上发布一个新问题,并展示相关的代码。祝好运。 - Steven
谢谢Steven,我已经发布了另一个问题:https://dev59.com/eU_Ta4cB1Zd3GeqPB5KA,只是为了澄清需要处理什么。 - webnoob
1
在我的ASP.NET应用程序中,一种经常与内存泄漏相关的类型是XmlSerializer。尽管它实现了IDisposable,但它会在内存中创建新的程序集,并且这些程序集永远不会被处理(一旦程序集加载到进程中,它就会一直存在,直到进程终止)。 - Vinicius

6

你的内存使用率很高可能有很多原因,但.NET中的垃圾回收是一个非常精确的事情。也就是说,它可以为你做很多事情,但有时不如你所期望的那样。

具体而言,它只能清理掉没有活动引用的对象,因此如果你完成了一个类,但仍然有某些东西引用它,你需要删除该引用,以便GC可以为你恢复该内存。此外,如果你有任何非活动的打开连接(比如到数据库或其他地方),不要忘记关闭和处理它们。通常,我们会像这样将这些对象包装在using块中:

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

这将自动关闭和处理连接。它是作为try...finally块实现的,因此即使在using块中抛出异常,连接也将被关闭。

除此之外,答案就是“性能分析,性能分析,性能分析”,直到找到您的泄漏/瓶颈/任何问题。


1
是的,使用“using”语句相当于在“finally”块中调用“Dispose”并使用“try”-“finally”块。 - Steven

3

以下是需要注意的几个问题:

首先,您是否在使用会话?它们是进程内还是SQL会话?如果是进程内的,超时设置为多少?如果超时时间非常长,这可能会解释为什么您会使用如此多的内存(用户会话将被存储很长时间)。

其次,对象的处理。.NET垃圾回收器会为您清除引用,但是当您创建实现IDisposable接口的对象时,应始终使用using关键字。

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

这相当于执行以下操作:
Foo foo;
try
{
    foo = new Foo();
    ...
}
finally
{
    foo.Dispose();
}

这样做可以确保您高效地处理对象。

如果您在代码中仍然找不到明显的问题,可以通过对调用最多的方法进行分析来优化性能。您可以在这里找到关于好的分析工具的信息。这一定会帮助您找到问题的根源。


1

尽管在.NET中内存泄漏依然有可能发生。虽然对于大多数对象你不需要手动释放(如Graphics对象等仍需手动释放),但是如果你保存了这些对象的引用,它们将不会被垃圾回收,因为它们仍被引用着。

如果GC发现一个对象在应用程序的任何位置被引用,则不会将其清除。

请参阅Code Project有关.NET内存泄漏及如何定位和修复它们的文章。


1

如果你的ASP.NET应用程序使用事件(哪个不使用呢) 那么你必须确保取消订阅事件

经验法则:

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

这个主题有很多 资源, [在这里][5], 我曾经在我工作的应用程序中遇到过一些痛苦,因为普通程序员没有意识到事件可能会导致内存泄漏。

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


在大多数情况下,取消订阅是不必要的。只有在您订阅了长期存在的对象上的事件时才需要。像单个页面上的控件之间的事件不需要手动清理:https://dev59.com/GEXRa4cB1Zd3GeqPv_FF - Dan Is Fiddling By Firelight

1

当内存超过500 MB时,请使用内存分析器(无论是在内存转储还是在实时环境中)来获取占用所有空间的对象的线索。

在开发环境中进行分析时,您可能会发现在浏览站点时只有一个特定的对象类型增长,而其他对象则保持全局不变。


0

虽然不必显式释放内存,垃圾回收器会为您完成这项工作,但代码中可能会无意中保留对对象的引用以防止其被收集 - 这实际上是一种内存泄漏。

在ASP.NET中,只要应用程序和会话缓存引用了对象,它们就会保持“活动状态”。在不知道您的应用程序的情况下,我建议您获取一个内存分析器,并更仔细地查看内存中到底有什么,以及应用程序或会话缓存是否保留了不应该保留的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接