ASP.NET应用程序的内存使用过高

5
我们的ASP.Net应用程序存在问题。其中一些应用程序从开始时就会占用大量内存作为它们的工作集。
在我们的2个WebFarm服务器(每个服务器有4GB RAM)上运行多个应用程序。我们拥有一个稳定的环境,剩余约1.2GB的内存。
然后我们添加了一个MVC5 + WebApi v2 + Entity Framework应用程序,它立即要求1GB以上的工作集内存,而实际上只使用约300MB。这导致其他应用程序抱怨没有足够的内存剩余。
我们已经尝试设置虚拟内存和私有内存的限制,但都没有效果。如果我们将其设置为大约500MB,该应用程序仍然使用大约相同数量的内存(远远超过500MB),并且似乎不遵守所实施的限制。
作为参考,我使用空的MVC5项目(VS2013模板)进行测试,这已经占用了300MB的内存,而实际上只使用了约10MB。
将应用程序设置为32位应用程序似乎有助于减少工作集的大小。
是否有任何方法可以减小工作集的大小,或者强制对其大小进行硬限制?
编辑:在使用Web Api v2和Entity Framework的项目中出现巨大的内存使用情况时,我的API控制器如下:
namespace Foo.Api
{
public class BarController : ApiController
{
    private FooContext db = new FooContext(); 

    public IQueryable<Bar> GetBar(string bla)
    {
        return db.Bar.Where(f => f.Category.Equals(bla)).OrderBy(f => f.Year);
    }
}

在我找到的大多数教程中(包括微软的教程),它们看起来都是这样的。但在此处使用using并不起作用,因为LINQ会延迟加载。如果我到处添加ToList(未经测试),它可能会起作用,但这会有什么其他影响吗?
编辑2: 如果我这样做,它就可以工作。
namespace Foo.Api
{
public class BarController : ApiController
{
    public List<Bar> GetBar(string bla)
    {
        using(FooContext db = new FooContext){
           return db.Bar.Where(f => f.Category.Equals(bla)).OrderBy(f => f.Year).ToList();
        }
    }
}

ToList()这个方法会影响api的性能吗?(我知道我不能像使用IQueryable一样廉价地继续查询)
编辑3: 我注意到应用程序的私有工作集非常高。有没有办法限制它?(不引起不断重启的情况下)
编辑4: 据我所知,每个APIController都有一个Dispose方法。我的前端只是一些简单的MVC控制器,但大部分是.cshtml和JavaScript文件(angular)。
我们还有另一个应用程序,只是常规的mvc,有两个模型和一些简单的视图(没有数据库或其他可能泄漏的外部资源),这也占用了4-500mb的内存。如果我进行剖析,我看不到任何指示内存泄漏的东西,我确实看到只有10或20 mb被实际使用,其余的是未分配的非托管内存(但是是私有内存工作集的一部分,因此由该应用程序声明并且无法被任何其他应用程序使用)。

2
通过声明该内存,它会使其他应用程序(在同一服务器上)无法使用它。有另一个具有不同内存使用情况的应用程序,因为所有内存都被声明而抱怨。我们只想限制它可以声明多少虚拟内存,以便如果需要,其他应用程序有更多的内存可用。 - Kevin
2
这是另一个通过IIS运行的Web应用程序。我们正在进行分析,发现有大量(例如700MB以上)的内存仅为.NET保留,但未被使用。稍后我会尝试添加屏幕截图。 - Kevin
@KWyckmans - 听起来你的应用程序正在共享同一个应用程序池。你应该将它们放在不同的池中,这样它们就会有自己的工作进程和地址空间。 - Erik Funkenbusch
每个网站都有自己的应用程序池和工作进程。 - Kevin
@KWyckmans 你为什么认为它在使用那个内存?你了解 GC 工作的原理,以及当它们拥有比所需内存多一倍时如何更好地工作吗?事实上,在托管内存环境中,除非出现问题,否则应该让环境管理内存。 - Aron
显示剩余5条评论
2个回答

4
我曾经遇到过一些应用程序类似的问题。通过使用 using 语句包装可处理的数据库资源,我成功地解决了这个问题。
对于 Entity Framework,这意味着确保在每个请求后始终关闭上下文。连接应在请求之间被释放。
using (var db = new MyEFContext())
{
   // Execute queries here
   var query = from u as db.User
               where u.UserId = 1234
               select u.Name;

   // Execute the query.
   return query.ToList();

   // This bracket will dispose the context properly.
}

你可能需要将上下文封装到一个服务中,该服务会对您的上下文进行请求缓存,以便在整个请求过程中保持其活动状态,并在完成后处理它。
或者,如果使用单个上下文来作为整个控制器的模式(就像MSDN示例中那样),请确保重写Dispose(bool)方法,就像这里的示例一样。
protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        db.Dispose();
    }
    base.Dispose(disposing);
}

因此,您的控制器(来自上面)应该如下所示:

namespace Foo.Api
{
    public class BarController : ApiController
    {
        private FooContext db = new FooContext(); 

        public IQueryable<Bar> GetBar(string bla)
        {
             return db.Bar.Where(f => f.Category.Equals(bla)).OrderBy(f => f.Year);
        }

        // WebApi 2 will call this automatically after each 
        // request. You need this to ensure your context is disposed
        // and the memory it is using is freed when your app does garbage 
        // collection.
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

我看到的行为是,应用程序会消耗大量内存,但它可以垃圾回收足够的内存,使其永远不会出现OutOfMemoryException。这使得找到问题变得困难,但处理数据库资源解决了问题。其中一个应用程序以前使用了约600 MB的RAM,现在只使用约75 MB。
但是,这个建议不仅适用于数据库连接。如果你遇到内存泄漏问题,任何实现IDisposable的类都应该受到怀疑。但由于你提到你正在使用EntityFramework,它最有可能是罪魁祸首。

1
如何在Web Api v2中使用这个呢?由于linq的延迟加载,原样使用是行不通的。 - Kevin
@NightOwl888,您能详细说明一下自定义的IControllFactory吗? 此外,IoC模式是否是控制释放这些对象的可能解决方案?(例如,让IoC将服务/存储库甚至特定的DbContext注入到控制器中。通过这样做,IoC接管了释放的控制权)?只是一个想法... - Yves Schelpe
@YvesSchelpe - 只有当您使用的容器具有一种方法来连接每个请求对象并具有正确处理它的机制时,使用DI才能帮助解决此问题。并非所有容器都具备这些功能。在我看来,修复内存泄漏不是开始使用DI的有效原因,但如果您正在使用DI,则仍必须确保正确处理未受管控的资源(例如数据库连接和文件流),只是确保方式转移到应用程序的不同部分。 - NightOwl888
@NightOwl888 当然,使用DI来修复内存泄漏并不是预期的方法。这不是我意思,更多的是在于KWyckmans上面问你那个问题的心态... 我知道StructureMap和AutoFac容器确实处理对象的释放,当然前提是你设置得当。无论如何,正如我所说,这只是为了让它适应KWyckmans提出的情景。 - Yves Schelpe
我认为我已经在我使用的每个API控制器上实现了这个,但我需要重新检查。感谢您提供的额外信息。 - Kevin
我正在尝试找出当前服务器高内存使用率的罪魁祸首,这是我想到的第一件事情,然而阅读更多相关资料后发现,处理DataContexts并不是非常重要的,你可能需要继续寻找内存问题的真正来源:https://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext/ - Jamie Twells

1

移除所有Telerik Kendo MVC引用(dll等)解决了我们的问题。如果我们在没有这些引用的情况下运行应用程序,所有的内存问题都消失了,我们看到正常的内存使用。

基本上:是一个外部库导致了高内存使用率。


1
你是怎么想到的?有没有一种方法可以显示每个库的内存使用情况? - Daniel Ribeiro

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