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

19
基本区别在于,弱引用应该在每次GC运行时被声明(保持内存占用低),而软引用应该一直保留在内存中,直到GC实际上需要内存(它们试图扩展生命周期但随时可能失败,这对于例如缓存尤其是相当昂贵的对象很有用)。
据我所知,在.NET中没有明确说明弱引用如何影响对象的生命周期。如果它们是真正的弱引用,它们根本不应该影响对象的生命周期,但那也会使它们对于缓存(我在这里错了吗?)等主要用途变得相当无用。另一方面,如果它们像软引用那样工作,它们的名称有点误导人。
就个人而言,我认为它们的行为类似于软引用,但这只是一种印象,没有依据。
当然,实现细节也适用。我正在询问与.NET的弱引用相关联的心态-它们能够扩展生命周期,还是像真正的弱引用那样工作?
(尽管我提出了许多相关问题,但我还没有找到答案来解决这个具体问题。)

除了调用GC.Collect()之外,我从未听说过你可以对对象何时被GC回收有任何影响。从这个角度来看,WeakReference所表达的唯一含义就是该对象可被回收。 - flq
你是否在将Java范例应用于C#时出现了问题?在C#中,弱引用通常是指可以随时被GC回收的引用。 - Ben Robinson
@BenRobinson:应该仍然有可能区分。除非他们明确希望在这个问题上保持模糊(这完全有可能)。 - mafu
@CodeInChaos:我从迄今为止阅读的有关此主题的来源中得出了这种印象,尤其是MSDN。示例往往展示软行为比弱行为更可取的情况。 - mafu
@malfruct应该这样做吗?为什么?你可以在Java中做到,因此你应该能够在C#中做到并不是一个特别有效的论据。根据维基百科关于弱引用的文章,软引用只是Java中可用的三种“非强”引用类型之一。C#没有这三种类型,因此您正在将Java功能与C#中不存在的东西进行比较。但正如Code in Chaos所说,弱引用对其引用的对象的生命周期没有影响,因此它们确实映射到Java的弱引用而不是软引用。 - Ben Robinson
显示剩余3条评论
3个回答

14

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集合中积极收集弱引用对象)正是使得弱引用成为缓存的糟糕选择的原因。如果您尝试在缓存中使用弱引用,那么您会发现您的缓存因无法理由地频繁清空而失效。


2
我不确定我能够理解。我没有说弱引用应该用于缓存,但是软引用应该使用(我认为你也是这个意思)。MSDN指出,“C# 弱引用”可以用于缓存。因此,如果它们确实像软引用一样运行,那么 C# 弱引用应该被称为“软引用”。 - mafu
我已经增加了两个演示我所提到行为的程序来补充我的答案。如您所见,弱引用的行为不像软引用那样。您引用的MSDN文章中的例子是错误的。您不应该像那样使用弱引用。那不是它们被设计解决的问题,而且它们对于缓存是一个糟糕的解决方案。弱引用的主要实际应用包括修饰数据结构和允许GC收集不可达的子图在可变图中。 - J D
“soft refs should”应该翻译为“软引用应该”。实际上,我也不会使用软引用来进行缓存。我会更智能地跟踪内存消耗并清除缓存行(例如最近最少使用),而不是让GC以不受控制的方式随机清除它们。显然,.NET 4提供了这样一个开箱即用的缓存,但我从未使用过。 - J D
我完全同意你的第一条评论。MSDN中的示例是误导性/错误的,“WeakReference”的命名是正确的。 - mafu

9
我没有看到任何信息表明它们会增加所指向对象的生命周期。而我阅读的有关GC用于确定可达性的算法的文章也没有以这种方式提及它们。因此,我预计它们不会对对象的生命周期产生影响。
弱引用:这种句柄类型用于跟踪对象,但允许其被收集。当对象被收集时,GCHandle的内容将被清零。弱引用在终结器运行之前被清零,因此即使终结器复活对象,弱引用仍然被清零。
跟踪弱引用复活:这种句柄类型类似于弱引用,但如果对象在终结期间被复活,则句柄不会被清零。

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


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

  • 对象的代比垃圾回收的代大。这对于大对象特别有意义,因为它们分配在大对象堆上,并且始终被视为Gen2。
  • 具有终结器的对象及其所有可达对象都可以在垃圾回收后幸存。
  • 可能存在一种机制,旧对象的先前引用可以保持年轻对象的生存,但我不确定。

经进一步阅读,似乎这是正确的答案。但我仍然找不到明确说明“不以任何方式影响生命周期”的来源。 - mafu
“Weak”与“WeakTrackResurrection”的描述模糊且不准确。当一个对象变得符合立即终结的条件时,弱引用将被清除;而弱引用跟踪复活则会一直有效,直到对象停止存在或者该句柄的所有者使其无效。不幸的是,当要求WeakReference创建一个WeakTrackResurrection句柄时,除非存在对WeakReference本身的强根引用,否则它很容易过早地销毁。 - supercat

-1

是的
弱引用不会延长对象的生命周期,因此一旦所有强引用超出范围,它就可以被垃圾回收。它们对于保留初始化昂贵但如果未被积极使用应该可供垃圾回收的大型对象非常有用。


1
让我在这里尝试非常精确 - 他们是尝试延长生命周期,还是不尝试?如果您能提供进一步阅读的来源,那将是很棒的。 - mafu
它们在决定是否对对象进行垃圾回收时被忽略了 - 它们不做决定,它们只是不保持对象的存活。 - TomTom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接