【问题标题】:Minimizing usage of RAM by created objects in runtime in C#在 C# 运行时通过创建的对象最小化 RAM 的使用
【发布时间】:2017-02-06 18:21:25
【问题描述】:

我想知道在创建对象、在 C# 中执行 LINQ 时的最佳实践是什么。例如,我意识到当我使用 LINQ 打开连接时,我应该将模型对象放入这样的 using 语句中:

using(var ctx = new mymodel())
{

}

现在,作为 EF 类的对象呢?

它们没有实现 IDisposable 接口,因此在创建这样的对象时我不能这样做:

using(var user = new Users())
{

}

但是当一个动作被这样调用时:

public ActionResult InsertUser()
{

    var user = new Users();

}

我不清楚插入数据库后该对象会发生什么。这个对象是在内存中分配还是被释放?如果不是,那么在不再需要内存后释放内存的最佳做法是什么......?

另外还有静态变量...

所以总结一下,我的问题是:

  • 在创建类的对象实例时释放内存的最佳做法是什么?

  • IDisposable 接口的实现是否在我有好的选择的每个类上实现?

  • 在 .NET MVC 中创建静态变量时,释放此类变量占用的内存的最佳方法是什么?

  • 同样的问题也适用于 Session 对象?

附:伙计们,如果你们所有人都在阅读本文以发表您的意见或为一些文档/博客文章发布一些有用的链接,以便我可以扩展我的视野,我将非常感激 =)

【问题讨论】:

  • 你有内存消耗的问题吗?
  • 对象超出范围时被收集。如果没有活动代码区域引用该对象或间接引用它的对象树,它将被收集。您只需要在具有需要清理的非托管资源的类上实现IDisposable
  • 这是一个过早优化的例子。你真的有性能问题吗?
  • 是的,我会说这绝对是相关的。是的,我的意思是它们是由垃圾收集器收集的。
  • 一如既往地阅读有关垃圾收集器的文档。它会告诉你什么是“非托管资源”。如果您有更具体的问题,请返回此处。

标签: c# performance entity-framework linq memory


【解决方案1】:

在您进行任何性能调整之前,我强烈建议您运行内存分析器(例如 JetBrains dotMemory,但还有其他)并找出问题的实际根源。如果没有来自分析器的信息,您的优化将就像将手指伸向天空并大喊“彩虹!”即充其量是无用的,最坏的情况是有害的。

在使用分析器确定问题之后,但在开始更改代码之前,我建议阅读有关垃圾收集如何在 .Net 中工作的信息。以下是一些帮助您入门的参考资料:

  1. MSDN Garbage Collection
  2. MSDN Garbage Collector Basics and Performance Hints
  3. .Net Garbage Collection in depth

这里有一些链接可以回答您的问题:

【讨论】:

  • @User987 为您的具体问题添加了更多链接
【解决方案2】:

回应评论。

当您创建常规 .NET 对象时,.NET 运行时知道它的一切,因为运行时创建了它。它知道它在内存中的位置,何时不再需要等等。当不再需要它时 - 运行时将回收它的内存。那是(由运行时)管理的对象。您不应该关心此类对象的内存管理。

现在以文件句柄为例。当您在 .NET 中打开文件时,它将将此操作委托给操作系统(例如,Windows)。此资源不由运行时管理,它由操作系统管理。因此,它是非托管资源。因此,使用文件的 .NET 库代码的作者(我的意思是创建 FileStream 和类似类的人,而不是使用它们)应该手动编写代码来释放此类资源(在这种情况下关闭文件句柄)。按照惯例,作者将对释放此类资源的代码使用IDisposable.Dispose 方法,并确保当底层对象(如FileStream)被GC 收集时,非托管资源也将被释放。因此,即使您忘记调用 Dispose - 当 GC 收集 FileStream 时,文件句柄也会被关闭(并不是说它不会神奇地发生,而是 因为 代码作者明确地让它像这样发生)。但这可能在未来的任何时候发生,您不想让文件句柄在未确定的时间内保持打开状态 - 因此,当您完成 IDisposable 对象时,您总是自己调用 Dispose。

这同样适用于大多数非托管资源。例如,数据库连接由数据库管理,您不想让数据库连接在未确定的时间内保持打开状态,因此您调用Dispose 并显式通知数据库关闭它(即使您不调用它 - 作者.NET 数据库代码在收集对象时会注意这样做)。 EF 上下文包装了数据库连接,因此它还实现了IDisposable。如果您不想在收集到上下文之前保持连接打开(而且您肯定不希望这样做) - 您自己致电 Dispose

或者假设您编写自己的代码来使用 ImageMagick 处理图像。那是 C 库,所以运行时不知道它的内部工作原理。您要求库为您的图像分配内存,但 .NET 无法回收此类内存 - 它不管理它,它由 ImageMagick C 库管理。所以你实现 IDisposable 并告诉 ImageMagick 在该方法中释放内存。

【讨论】:

    【解决方案3】:

    var user = new Users(); 没有实现IDisposable 的情况下,任何未处置的对象,只有在对该对象有活动引用的情况下才能保证存在;之后,垃圾收集器 (GC) 下次尝试释放内存时将有资格进行处置。

    在您上面的示例中,一旦离开方法InsertUser(),它将不再有任何指向它的引用,因此将进行垃圾回收。

    但是,如果存在引用,例如在以下代码中,则在清除引用或释放包含类之前不会释放对象。

    private User _insertedUser;
    
    public ActionResult InsertUser()
    {
    
        _insertedUser = new Users();
    
    }
    

    当应用程序需要释放一些内存时,垃圾收集器会触发。当它触发时,它会对内存中的对象执行几次扫描,以判断是否存在任何引用。它首先扫描自上次调用 GC 以来新创建的每个对象。在这次扫描中幸存下来的对象被提升了一代。如果扫描后仍需要更多内存,则执行第二代扫描。这将扫描在单个垃圾收集中幸存下来的每个对象,以查看它现在是否可以被释放。同样,如果一个对象幸存下来,它会向上移动另一代,总共扫描 3 代。

    此方法帮助 GC 执行内存管理,同时限制所涉及的高成本(较新的对象更有可能可供释放)。由于 GC 的成本很高,因此最好通过用户代码来处理对象,以帮助限制 GC 的调用次数。

    tldr;

    • 如果对象实现了 IDisposable,则将其释放;一个简单的方法是 用“使用”包围它。
    • 如果无法释放对象并且不再需要它,请确保清除对该对象的所有引用(尤其是在创建该对象的方法之外的引用)。 这使 GC 能够从内存中释放它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-03
      • 1970-01-01
      • 2022-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-30
      • 1970-01-01
      相关资源
      最近更新 更多