【问题标题】:Weird array memory management in .NET.NET 中奇怪的数组内存管理
【发布时间】:2015-12-09 08:57:30
【问题描述】:

我编写了一个 WPF 应用程序,目标是 .NET 4.5.2 64 位。它在 64 位 Windows 8.1 Professional 上运行。

该应用程序用于同时录制多个视频。因为视频编码器的速度在运行过程中会发生很大的变化,所以我在内存中制作了一些非常大的字节数组(总共 6 GiB),这足以让我缓冲 40 秒的视频,我让编码发生在一个单独的线程上,因此我不会冒险被编码阻塞并错过来自相机的传入帧。

我在某处读到(字节)数组在 .NET 中被延迟分配 - 即仅写入 byte[] foo = new byte[1024*1024]; 并不意味着立即分配 1 MiB;直到第一次访问才实际分配内存。虽然我再也找不到我在哪里读到的了。无论如何,知道这一点,我一定要在我的数组上调用Array.Clear(),这应该强制它们立即被分配。我还需要确保缓冲区始终保留在 RAM 中,因此我完全禁用了页面文件。

但由于某种原因,似乎 .NET 运行时或 Windows 仍在做一些疯狂的事情:在初始化我的字节数组并在所有它们上调用 Array.Clear() 之后,我可以在任务管理器中看到 7.4 GiB 正在使用中。它坐在那里直到我按下“记录”按钮,此时我开始有真实的数据来填充缓冲区。奇怪的是,In Use 内存开始下降到 3.6 GiB 左右,然后又慢慢回升至 7.4 GiB。我的应用程序从未丢弃和重新分配数组。还要记住,因为我已经禁用了页面文件,所以 Windows 没有地方可以调出页面。

发生了什么事?我应该如何解释这种怪异?

【问题讨论】:

  • 数组是在 .NET 中延迟分配的 不,这是操作系统处理虚拟内存的结果。当您在 .NET 中分配一个数组时,您会保留所有需要的虚拟内存。但是,有些虚拟内存可能不会分配给物理 RAM。为了更好地了解发生了什么,我建议您查看流程的性能计数器;而不是整个计算机的性能。顺便问一下,你有什么问题吗?
  • 可能有人认为您为这项工作选择了错误工具。使用一种编程语言来抽象您对内存的使用,然后不得不与操作系统抗争以防止它完成它的工作。如果您想显式控制内存,您应该在较低的抽象级别上工作,您可以在其中使用 Windows API,例如 VirtualLock

标签: .net windows memory-management


【解决方案1】:

发生了什么事?我应该如何解释这种怪异?

这并不奇怪,这就是垃圾回收的工作原理。

当您使用Array.Clear 时,您只是将这些字节重置为 0(感谢@Martin)。 这并不意味着垃圾回收将在同一时刻发生。如果数组仍然被引用,这甚至不会使它有资格被收集。 GC 的运行时间是不确定的。

您所看到的实际上是有道理的,因为您看到一旦您按下“记录”,并且运行时需要分配更多资源来使用,它首先调用一个集合,然后随着这些字节的消耗开始缓慢增加由您的应用程序。

正如其他人在 cmets 中所说,如果您需要对应用程序中管理的内存进行细粒度粒度,则最好使用较低级别的语言,该语言使您能够(尽可能)控制分配和释放记忆。

【讨论】:

  • 在字节数组上调用Array.Clear 不会使任何内存可用于内存收集。它只会将字节设置为 0。
  • 确实知道 Array.Clear 将字节设置为 0。这就是为什么我认为它本质上会强制访问我的数组并导致要分配的物理 RAM。如果我将此信息与@Luaan 的答案结合起来,我是否理解 Array.Clear 要么是预置零页面上的无操作,要么将我的数组转换为操作系统看到的预置零页面(等等操作系统决定将其指向 一个 预置零页面?
  • 它并不能真正解释下降。当我按下“记录”按钮时,1)我不需要额外的资源; 2) 即使 GC 开始收集,它也不应该收集我的数组,我仍然有对它们的引用。
  • @Kal 由于您没有向我们提供minimal reproducible example,因此我无法进一步了解这一下降。
  • @YuvalItzchakov 好的,我将尝试做一个触发此行为的最小示例。
【解决方案2】:

不,.NET 数组不是延迟分配的。然而,操作系统做了一个小技巧——当你在内存中分配一个预置零页面时(并且 .NET 总是分配预置零页面),它实际上不会给你 物理 RAM - 相反,所有您的页面指向(单个)零页面。在您真正将数据写入巨大的空数组之前,存储该数组数据的所有页面都是这一页。

这意味着虽然进程的虚拟内存始终如您所愿(例如 1024*1024 字节数组的兆字节),但物理 RAM 使用量却不是。这类似于操作系统决定将您的内存分页到页面文件时 - 您无法控制它,而且您确实不应该控制。

实际上,这对您的应用程序没有任何影响。您仍然拥有连续的虚拟内存空间,并且操作系统会在您真正需要时立即为您提供真实内存。除非您的系统处于内存压力之下,否则您几乎可以立即获得真正的内存(它可能需要归零)。

【讨论】:

  • 这可能正是我所说的延迟分配的意思:操作系统很聪明地使用了预置零的页面。您是否有文章的链接描述了这种预置零页面处理的工作原理?
  • @Kal 这确实是 Windows 内存管理的基础知识,不难找到所有细节。例如,请参阅此 MSDN 博客文章 - blogs.msdn.com/b/tims/archive/2010/10/29/…
猜你喜欢
  • 2016-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-11
  • 2014-10-10
相关资源
最近更新 更多