【问题标题】:.NET MemoryCache: How does it enforce memory limit?.NET MemoryCache:它如何强制执行内存限制?
【发布时间】:2016-11-26 23:30:11
【问题描述】:

.NET MemoryCache 是 C# 对象的缓存。一些对象可能具有复杂的结构,而另一些对象可能具有不安全的引用。 C# 是在为实现 PhysicalMemoryLimit 做一些魔术,还是只是计算每个对象的浅大小?

我怀疑是后者。尽管如此,如果我将同一个对象多次放入缓存中(例如,用于跟踪丢失的项目),是一次计算大小,还是对包含该实例的每个条目进行计算?

【问题讨论】:

  • 我很确定缓存会尝试计算缓存中对象的大小。这样做是徒劳的,因为 (a) 任意对象的大小在插入缓存后可能会发生变化,并且 (b) 缓存对象的字段引用的对象可能还有其他引用,因此从缓存中删除并不一定会释放其所有内存。
  • 不管做什么,都是通过这个方法实现的:referencesource.microsoft.com/#mscorlib/system/…
  • 更改了问题的内容。

标签: c# asp.net caching memory garbage-collection


【解决方案1】:

.NET MemoryCache 类似于 ASP.NET Cache 类。如果我们查看 ASP.NET 缓存,我们会看到一个名为 CacheItemRemovedCallback 的函数。当从缓存中删除一个项目时触发。

这个函数给出一个CacheItemRemovedReason 和回调函数。如果我们查看原因,我们会发现可以从缓存中删除某个项目,因为系统将其删除以释放内存。因此,虽然 PhysicalMemoryLimit 给出了缓存可以在单次运行中使用的物理内存百分比,但我认为他们将其留给系统以在达到限制时清除缓存。

如果您真的使用Add 函数将缓存项放入缓存中,它会将其添加为新的CacheItem 实例。所以会被多次计算。如果您使用函数AddOrGetExisting,它将检查该项目是否已经在缓存中。如果是这样,它将使用该实例而不是新实例。那么就只算一次。

希望这可以帮助您朝着正确的方向前进。

【讨论】:

  • 我不确定是否是这种情况。测量在整个 MemoryCache 上执行:referencesource.microsoft.com/#System.Runtime.Caching/System/…。所以它可以很好地只计算一次重复的对象。
  • 您也没有考虑 AddOrGetExisting 与不同的键但相同的值实例一起使用的场景。该值是计算一次还是多次?
【解决方案2】:

这里是来源。如果您查看调用 AddOrGetExisting 的 Add 方法的实现:referencesource.microsoft.com,您的第二个问题的答案是显而易见的。

我不知道大小,但我猜你的假设是正确的,根本没有魔法。另外,如果您有兴趣,可以深入检查来源。

【讨论】:

  • 那个来源没有回答任何问题。用户可以将同一对象的多个类型放在缓存中,在不同的键下。 MemoryCache 不检查该值是否已添加。它仅检查是否已添加键,并且出于缓存目的,该值通常占用更多空间。另外,该代码实际上并没有计算大小并做出任何驱逐项目以尊重内存限制的决定。
  • 这不是重点(至少据我了解)。出于缓存目的,对象本身并不重要。只有关键是重要的。这也是合理的,因为有相同的对象可以在整个系统中多次使用,它们需要分开存储,因为关键是描述对象在这个机制中的用途。
  • 还有对非托管代码的调用以获取当前内存使用情况:if (UnsafeNativeMethods.GlobalMemoryStatusEx(ref memoryStatusEx) != 0) { s_totalPhysical = memoryStatusEx.ullTotalPhys; s_totalVirtual = memoryStatusEx.ullTotalVirtual; } 稍后在计算压力时使用 if (memory >= 0x100000000) { _pressureHigh = 99; } else if (memory >= 0x80000000) { _pressureHigh = 98; } 也许我错过了你的意思,但来源中有很多答案。
  • 所以它可能是直接从操作系统获取缓存大小。
  • 哦,神奇的事情正在发生...查看 SRef 的代码。这是一个特殊的“大小引用”,它使 GC 报告它指向的对象图的大小。
【解决方案3】:

您无法轻松设置 MemoryCache 类的 PhysicalMemoryLimit 或 CacheMemoryLimit 成员,因为它们没有实现设置器(至少在 .Net 4.0 版本中)。

我同意其他答案,如果您只想缓存一个对象的单个实例,则应使用 AddOrGetExisting。如果没有,您可以使用不同的键缓存备用项目。

【讨论】:

【解决方案4】:

阅读文档,缓存似乎没有尝试计算它正在缓存的对象的大小。这是有道理的,因为它不是可以在进程本身内为任意类型完成的事情(您可以为固定大小的结构或固定大小的结构数组执行此操作,但仅此而已);一些谷歌搜索将向您确认这一点。但是,它确实知道计算机上有多少可用 RAM。你可以自己从new Microsoft.VisualBasic.Devices.ComputerInfo().AvailablePhysicalMemory 得到这个。所以大概缓存做了两件事:

  1. 它会跟踪每个对象上次使用的时间。
  2. 每隔一段时间轮询内存统计信息。

然后在每次轮询时,可用内存量要么在可接受的范围内,要么不在。如果它在可接受的范围内,它什么也不做。如果不是,它会开始删除项目,最后一次访问的项目最先被删除。它会不断删除项目,直到内存恢复到可接受的范围内。

如果您考虑一下,您可以使用缓存中可用的信息做的几乎所有事情。

这种策略是可以的,但是如果你有其他对象持有对缓存中项目的引用,它显然会失效,因为从缓存中删除项目不会释放它以进行垃圾回收。这就是回调的重点,执行清理以确保不再有对该对象的引用。

【讨论】:

  • 如果您查看源代码,您会发现它确实获得了缓存中对象的大小。它使用一个特殊的指针来指示垃圾收集器在其收集周期内估计对象图的大小。 MemoryCache 的文档严重缺乏。例如,这种内存计数方法意味着缓存项应该是完全独立的,没有对“父”对象的引用,否则对象图的大小是完全没有用的。
猜你喜欢
  • 1970-01-01
  • 2011-07-29
  • 1970-01-01
  • 2015-10-25
  • 2011-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-17
相关资源
最近更新 更多