【问题标题】:Are C# weak references in fact soft?C# 弱引用实际上是软的吗?
【发布时间】:2011-12-07 01:02:53
【问题描述】:

基本的区别是弱引用应该在每次运行 GC 时声明(保持低内存占用),而软引用应该保留在内存中,直到 GC 实际需要内存(它们试图延长生命周期但可能随时失败,这对于例如缓存非常有用,尤其是相当昂贵的对象)。

据我所知,对于弱引用如何影响 .NET 中对象的生命周期,没有明确的说明。如果它们是真正的弱引用,那么它们根本不应该影响它,但这也会使它们对它们的缓存主要目的毫无用处(我认为我错了吗?)。另一方面,如果他们表现得像软引用,他们的名字就会有点误导。

就我个人而言,我认为它们的行为类似于软引用,但这只是一种印象,没有根据。

当然,实施细节也适用。我在问与.NET 的弱引用相关的心态——它们是否能够延长生命周期,或者它们的行为是否像真正的弱引用?

(尽管有很多相关问题,但我还没有找到这个特定问题的答案。)

【问题讨论】:

  • 除了调用 GC.Collect() 之外,我从未听说过您可以对 GC 回收对象的确切时间产生任何影响。从这个角度来看,弱引用唯一声明的是对象是可回收的。
  • @flq:例如,在 Java 中,可以通过选择软引用或弱引用来实现。 Soft 基本上的意思是“我不介意这是否被声明,但请尽量保留它”。
  • 你不是在 C# 中应用 Java 范式吗,在 C# 中,通常弱引用只是对 GC 随时可以收集的东西的引用。
  • @BenRobinson:应该仍然可以区分。除非他们明确希望在这件事上保持模棱两可(这是完全可能的)。
  • @CodeInChaos:我从迄今为止阅读的有关该主题的资料中获得了这种印象,主要是 MSDN。这些示例往往显示了更可取的情况是软行为而不是弱行为。

标签: c# .net garbage-collection weak-references soft-references


【解决方案1】:

C# 弱引用实际上是软的吗?

没有。

我错了吗?

你错了。弱引用的目的绝对是不是在你的意思上缓存。这是一个常见的误解。

它们是否能够延长生命周期,或者它们的行为是否像真正的弱引用?

不,它们不会延长使用寿命。

考虑以下程序(F# 代码):

do
  let x = System.WeakReference(Array.create 0 0)
  for i=1 to 10000000 do
    ignore(Array.create 0 0)
  if x.IsAlive then "alive" else "dead"
  |> printfn "Weak reference is %s"

此堆分配一个空数组,该数组可立即进行垃圾回收。然后我们循环 10M 次分配更多无法访问的数组。请注意,这根本不会增加内存压力,因此没有动机收集弱引用引用的数组。然而程序会打印“Weak reference is dead”,因为它仍然被收集了。这是弱引用的行为。软引用会一直保留到实际需要它的内存为止。

这是另一个测试程序(F#代码):

open System

let isAlive (x: WeakReference) = x.IsAlive

do
  let mutable xs = []
  while true do
    xs <- WeakReference(Array.create 0 0)::List.filter isAlive xs
    printfn "%d" xs.Length

这会不断过滤掉死的弱引用,并在链表的前面添加一个新的,每次都打印出链表的长度。在我的机器上,这永远不会超过 1,000 个幸存的弱引用。它在循环中上升然后下降到零,大概是因为所有弱引用都在每个 gen0 集合中收集。同样,这是弱引用而不是软引用的行为。

请注意,这种行为(在 gen0 集合中积极收集弱引用对象)正是使弱引用成为缓存的错误选择的原因。如果您尝试在缓存中使用弱引用,那么您会发现您的缓存无缘无故地被刷新了很多次。

【讨论】:

  • 我不确定我能跟上。我没有说弱引用应该用于缓存,但软引用应该(我想你说的也是)。 MSDN 声明“C# 弱引用”可用于缓存。因此,如果 C# 弱引用实际上表现得像软引用,那么它们应该被称为“软”。
  • 我用两个程序来增强我的答案,这些程序演示了我所指的行为。如您所见,弱引用的行为不像软引用。您引用的 MSDN 文章中的示例是一个不好的示例。你不应该使用这样的弱引用。这不是他们旨在解决的问题,而且它们是缓存的糟糕解决方案。弱引用的主要实际应用是修饰数据结构并允许 GC 收集可变图中无法访问的子图。
  • “软引用应该”。实际上,我也不会使用软引用进行缓存。我会更智能地跟踪内存消耗并清除缓存行(例如最近最少使用的),而不是让 GC 以不受控制的方式随机清除它们。显然 .NET 4 提供了这种开箱即用的缓存,但我从未使用过它。
  • 我完全同意你的第一条评论。 MSDN中的例子有误导/错误,“WeakReference”的命名是正确的。
【解决方案2】:

我没有看到任何信息表明它们会增加它们指向的对象的生命周期。我读到的关于 GC 用于确定可达性的算法的文章也没有以这种方式提及它们。所以我希望它们对对象的生命周期没有影响。


此句柄类型用于跟踪对象,但允许收集它。收集对象时,GCHandle 的内容将归零。弱引用在终结器运行之前归零,因此即使终结器复活对象,弱引用仍然归零。

WeakTrackResurrection
此句柄类型与 Weak 类似,但如果对象在终结期间复活,则句柄不会归零。

http://msdn.microsoft.com/en-us/library/83y4ak54.aspx


有一些机制可以使无法访问的对象在垃圾回收中幸存下来。

  • 对象的生成大于发生的 GC 的生成。这对于分配在大对象堆上并且为此目的始终被视为 Gen2 的大对象特别有趣。
  • 具有终结器的对象以及可从它们访问的所有对象都在 GC 中存活。
  • 可能存在一种机制,旧对象的先前引用可以使年轻对象保持活动状态,但我不确定。

【讨论】:

  • 经过进一步阅读,似乎这是正确的答案。不过,我仍然找不到明确声明“不会以任何方式影响生命周期”的来源。
  • “Weak”与“WeakTrackResurrection”的描述含糊不清,并不十分准确。一旦对象有资格立即完成,弱句柄就会被清除; WeakTrackResurrection 句柄将保持有效,直到对象不再存在或句柄的所有者使其无效。不幸的是,当要求 WeakReference 创建 WeakTrackResurrection 句柄时,它很容易过早地对其进行核对,除非存在对 WeakReference 本身的强根引用。
【解决方案3】:

是的
弱引用不会延长对象的生命周期,因此一旦所有强引用都超出范围,就可以对其进行垃圾回收。它们对于保持初始化成本很高的大型对象很有用,但如果它们没有被积极使用,它们应该可以用于垃圾收集。

【讨论】:

  • 让我在这里尽量准确一点——他们是否尝试延长寿命?如果您可以链接到源以供进一步阅读,那就太好了。
  • o,他们只是忽略了决定是否 gc 一个对象——他们没有做出决定,他们只是不让一个对象保持活动状态。
猜你喜欢
  • 2011-03-11
  • 1970-01-01
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
  • 2011-09-29
  • 2013-03-09
  • 2014-05-30
  • 2016-03-09
相关资源
最近更新 更多