【问题标题】:.NET memory management.NET 内存管理
【发布时间】:2011-11-11 09:19:30
【问题描述】:

谁能提供 .NET 内存管理的高级概述?

更具体地说,我正在寻找内存管理的概述..

  • 是否存在整体 .net 堆?
  • 是否基于堆应用程序?
  • 当我运行我的应用程序时,是创建了一个新堆/分配了内存,还是来自整个 .net 堆的内存?
  • 当 .net 堆耗尽其原始内存时会发生什么?它对操作系统有更多要求吗?
  • 基础知识对我来说是一个很好的开始,然后继续阅读更多内容

【问题讨论】:

标签: .net memory-management


【解决方案1】:

我在这篇关于 .net 内存管理的文章中讨论了以下问题。我希望这能让读者了解 .net 内存管理。

  1. 如何为值类型分配内存?
  2. 如何为引用类型分配内存?
  3. 内存是如何分配给静态字段和全局变量的?
  4. 多线程环境下如何分配内存?
  5. 如何在继承中分配内存?
  6. GC 是如何工作的?
  7. 什么是内存管理最佳实践?

让我们开始吧 .net 类型的内存分配发生在堆栈或堆中。我们需要了解什么时候在堆栈或堆中分配类型。

堆叠

方法中的所有局部变量都在堆栈中分配内存。 当一个方法 A 调用另一个方法 B 时,调用方法 A 的返回地址存储在 STACK 中,并将控制权传递给方法 B。 方法 B 执行完成后,它(连同其本地数据)从 STACK 中删除,并从方法 A 中停止的位置继续执行。

在检查堆中存储的内容之前,我们需要了解根引用。 让我们用一个例子来理解——

假设有一个包含员工技能列表的员工类。如果我们需要访问员工的技能,我们需要创建一个员工类的对象,然后访问该列表。

所以这里员工类型的对象引用是存储在堆栈上的根引用,实际对象存储在堆上

所有全局变量、静态全局变量和类型(使用 new 初始化时)都存储在 HEAP 中。

多线程环境中的内存分配 每个线程都有自己的 STACK,但 HEAP 在所有线程之间共享。因此,在 HEAP 的情况下确保线程安全变得很重要,因为不同的线程可以访问/修改相同的资源。

线程安全超出了本文档的范围。

继承中的内存分配 当我们创建子类的对象时,会在堆中创建单个对象。 该对象将保存类的所有状态相关数据,包括父类。

垃圾收集 (GC) .net 使用垃圾收集来自动清理分配的对象。

垃圾收集器检查堆上没有被任何东西引用的分配对象,或者我们可以说没有根引用。

静态变量在堆上分配并且永远不会被垃圾回收,因为它们永远不会有根引用。

GC 在单独的线程上运行,用于收集未使用的对象并释放内存。它会定期或在应用程序开始耗尽内存时自动运行。

开发人员可以通过执行 GC.Collect() 来运行 GC,但不建议这样做,因为它可能会影响应用程序性能。

GC 世代

根据对象的生命周期,它可以分为 3 类——

0代——对象刚创建的时候,放在0代。表示这个对象是新的,还没有被GC检查。

第 1 代 – 被 GC 检查过一次但由于具有根引用而存活的对象被放入第 1 代。

第 2 代 - 通过两次或更多检查并且由于具有根引用而未被 GC 杀死的对象属于第 2 代。

GC 不收集文件、网络、数据库、UI 元素等非托管资源的对象。 我们需要显式使用 Dispose (IDisposable) 或在 using 语句中使用相关的类对象(确保 IDiposable 在我们想要使用 dispose 的类型中被继承)。

内存管理最佳实践

  1. 使用 IDisposable 或 using 语句释放非托管资源。

  2. 如果在创建类对象期间不需要类成员的初始化,请尝试推迟初始化。

  3. 如果在创建列表期间未定义大小,则 List 等集合会将初始大小设置为 4 个元素。如果我们在 4 之后添加任何元素,则集合大小在内存中增加一倍。 因此,为避免这种情况,并且在列表较小时不占用额外内存,建议指定集合的​​初始大小。

  4. 尽量保持数据模型简单且结构良好。因为如果太复杂,GC 会花更多时间分析整个图以检查可以收集哪些对象。

  5. 尽可能使用 yield 语句。 C# 中的 Yield 关键字用于生成迭代器模式,它是一种 IEnumerator 实现。使用 yield 语句的好处是整个集合不需要在内存中。它一次处理一项。

  6. 避免在 LINQ 查询中过度分组或聚合函数。

谢谢!

【讨论】:

    【解决方案2】:

    是否有一个整体的 .net 堆?

    有很多。您通常关心的是第 0 代、第 1 代和第 2 代垃圾收集堆、大对象堆和加载器堆。世代有助于使垃圾收集器更有效。 LOH 用于太大而无法移动的对象。加载器堆存储静态变量值。

    堆是基于应用程序的吗?

    不,它们是基于 AppDomain 的。 AppDomain 为进程提供了一种廉价的替代方案。

    当我运行我的应用程序时,是创建了一个新堆/分配了内存,还是来自整个 .net 堆的内存?

    默认 CLR 在您的代码开始运行之前创建主 AppDomain 及其关联的堆。

    当 .net 堆耗尽其原始内存时会发生什么?它对操作系统有更多要求吗?

    是的。

    【讨论】:

    • 应用域是自动创建的,还是我们必须在应用中定义/构建的东西?
    • 在第3个答案中指出:默认CLR自动创建主AppDomain。其他人需要代码。
    • 啊,你确实说过 - 对不起,我看错了 :) soo.. 当一个进程被创建时,主 appdomain 也是。如果我们想要更多的应用程序域,则需要代码。我们如何知道要添加我们后续的 appdomains 的进程呢?这只是我们应该知道的吗,例如,我们可能已经制作了 app1:process1 并且已经知道它的存在,因此可以以编程方式将第二个、第三个等 appdomain 添加到该特定进程?
    • 我不禁注意到这些后续问题可能有点放错了地方。你为什么不问问你接受了答案的人?
    • 我是 stackoverflow 的新手,所以仍然要掌握如何适当地讨论 :) 我认为 id 在您回复之前已经接受了之前的答案。我很乐意给你们两个答案,因为你们都很好地帮助我了解了这个问题,但我认为它不能让我这样做
    【解决方案3】:

    每个进程都有自己的堆 - 如果在 GC 清理完所有可能需要的内存后需要更多内存,则该进程会向操作系统询问更多信息。

    我所知道的此类信息的最佳资源是 Jeffrey Richter 的 CLR via C# 书。

    【讨论】:

    • 所以.net 本身没有默认或整体的内存分配?只是我的应用程序(进程)在运行时会执行,如果在 GC 完成它的操作后需要更多内存,那么操作系统会请求更多内存吗?我的想法是否正确,如果此时操作系统无法为我的进程提供更多内存,那么会发回(OOM)内存不足异常?
    • @user799372:我不相信有任何默认限制,没有(不像 Java,比如说)。我相信 CLR 托管环境可以根据需要强制执行。是的,如果没有足够的内存来满足您的应用程序,您将获得 OOM。
    • 在我的进程创建的堆上,这有默认限制吗?或者,一旦我的进程启动并且堆已经“调整”到我的进程需求,那么限制是否计算?那么堆中的对象会被 GC 根据它们的属性和对象大小洗牌成正确的代吗?
    • @user799372:不确定你的意思——你的意思是 GC 在请求更多之前清除现有内存的工作量有多大?我相信这可能会根据不同的 CLR 版本而改变,但我认为它是动态的 - 它会根据以前的行为进行自我调整。
    • 我只是想知道当一个进程被创建时,它的伴随堆是否有一个总体大小限制(这反过来又会在阈值保持违反时触发 GC)
    猜你喜欢
    • 2012-01-18
    • 2014-10-10
    • 1970-01-01
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-13
    相关资源
    最近更新 更多