【问题标题】:Why are weak pointers useful?为什么弱指针有用?
【发布时间】:2009-09-28 03:32:41
【问题描述】:

我一直在阅读有关垃圾收集的文章,以寻找包含在我的编程语言中的功能,并且遇到了“弱指针”。来自here

弱指针就像指针, 除了来自弱的引用 指针不能防止垃圾 集合,弱指针必须 之前检查其有效性 它们被使用了。

弱指针与 垃圾收集器因为内存 他们所指的实际上可能仍然 有效,但包含不同的 对象比弱时 指针已创建。因此,每当一个 垃圾收集器回收内存,它 必须检查是否有 引用它的弱指针,以及 将它们标记为无效(这不需要 以如此幼稚的方式实现)。

我以前从未听说过弱指针。我想支持我的语言中的许多功能,但在这种情况下,我一生都无法想出一个有用的情况。为什么要使用弱指针?

【问题讨论】:

  • 我不知道......也许如果您只是暂时需要访问数据,垃圾收集器会比普通指针更快地清理它?

标签: pointers garbage-collection weak-references


【解决方案1】:

一个很大的问题是缓存。让我们思考一下缓存是如何工作的:

缓存背后的想法是将对象存储在内存中,直到内存压力变得如此之大以至于需要将某些对象推出(或者当然是显式无效)。因此,您的缓存存储库对象必须以某种方式保留这些对象。通过弱引用保持它们,当垃圾收集器因为内存不足而去寻找要消耗的东西时,仅由弱引用引用的项目将作为垃圾收集的候选对象出现。当前正在被其他代码使用的缓存中的项目将具有仍处于活动状态的硬引用,因此这些项目将受到垃圾回收的保护。

在大多数情况下,您不会滚动自己的缓存机制,但通常会使用缓存。假设您想拥有一个引用缓存中对象的属性,并且该属性在范围内保持很长时间。您会更喜欢 从缓存中获取对象,但如果它不可用,您可以从持久存储中获取它。如果压力太高,您也不希望强制该特定对象留在内存中。因此,您可以使用对该对象的弱引用,这将允许您获取它如果它可用,但也允许它从缓存中掉出。

【讨论】:

  • 这很有意义。谢谢。
  • 我倾向于同意 asaph 的观点;我之前已经实现了缓存,似乎有更好的方法来做到这一点。
  • @Imagist 当然可以通过其他方式构建更复杂的缓存。但是,我发现在许多情况下,我的第二个示例在与非常强大的缓存系统交互时会派上用场。
  • 在 Java 中,您应该使用 SoftReferences 而不是 WeakReferences 进行缓存(WeakReferences 比 SoftReferences 更积极地清除)。根据接受的答案,WeakReferences 应该用于附加对象属性等。
  • 我同意基思的观点。一个完美的 GC 会立即(并且没有开销)释放任何弱引用的对象,而这些对象不是同时被强引用的。实现者会非常努力地为您提供这个 GC。但是这种理想的 GC 会使您的“缓存”变得毫无用处,条目会从其中掉出,就像存储在筛子中一样。 GC 对程序的速度/内存消耗权衡一无所知。程序员知道(或应该知道)。因此,程序员应该决定使用多少内存来进行缓存。
【解决方案2】:

一个典型的用例是存储额外的对象属性。假设您有一个具有一组固定成员的类,并且您想从外部添加更多成员。因此,您创建了一个字典对象 -> 属性,其中的键是弱引用。然后,字典不会阻止键被垃圾收集;对象的删除也应该触发 WeakKeyDictionary 中的值的删除(例如通过回调)。

【讨论】:

  • 扩展方法与GC和弱引用完全无关
  • @lubos 我的猜测是,一旦将扩展方法加载到内存中,它就会一直存在,但是 可以 将其加载为弱引用以减少其对内存使用的影响.如果您将方法视为一种特定类型的对象属性,那么这正是 Martin 描述的用例。
  • @lubos:在实现方面,他们不必互相做任何事情。但是,扩展方法允许使用外部方法扩展类,就像我描述的方法允许使用附加属性扩展类一样。
  • @imagist 扩展方法只是静态方法的语法糖。它们与弱引用无关......
【解决方案3】:

如果您的语言的垃圾收集器无法收集循环数据结构,那么您可以使用弱引用使其能够这样做。通常,如果您有两个相互引用的对象,但没有其他外部对象引用这两个对象,则它们将成为垃圾回收的候选对象。但是,一个天真的垃圾收集器不会收集它们,因为它们包含对彼此的引用。

要解决此问题,您可以使一个对象对第二个对象具有强引用,而第二个对象对第一个对象具有弱引用。然后,当对第一个对象的最后一个外部引用消失时,第一个对象成为垃圾回收的候选对象,紧随其后的是第二个,因为现在它的唯一引用是弱的。

【讨论】:

  • 这对我来说似乎是一个糟糕的语言设计选择。需要程序员自己管理内存的垃圾收集在我的书中没有多大价值。
【解决方案4】:

另一个例子......不是完全缓存,但类似:假设一个 I/O 库提供一个包装文件描述符并允许访问文件的对象。当对象被收集时,文件描述符被关闭。希望能够列出所有当前打开的文件。如果您为此列表使用强指针,则文件永远不会关闭。

【讨论】:

    【解决方案5】:

    当您想要保留对象的缓存列表但不阻止这些对象在对象的“真正”所有者使用它时进行垃圾收集时,请使用它们。

    Web 浏览器可能有一个历史对象,它保存对浏览器在其他地方加载并保存在历史/磁盘缓存中的图像对象的引用。 Web 浏览器可能会使这些图像之一过期(用户清除了缓存、缓存超时等),但该页面仍然具有引用/指针。如果页面使用弱引用/指针,则对象将按预期消失,内存将被垃圾回收。

    【讨论】:

    • 弱指针的有效使用(我为有效使用制定了这个标准:“当设计明确区分存在指向对象的强指针的情况与没有”)。
    【解决方案6】:

    使用弱引用的一个重要原因是处理对象可能充当管道以将信息或事件源连接到一个或多个侦听器的可能性。如果没有任何侦听器,则没有理由继续向管道发送信息。

    例如,考虑一个允许在枚举期间更新的可枚举集合。集合可能需要通知任何活动的枚举器它已被更改,因此这些枚举器可以相应地调整自己。如果某些枚举器被其创建者抛弃,但该集合拥有对它们的强引用,那么只要该集合存在,这些枚举器就会继续存在(并处理更新通知)。如果集合本身在应用程序的整个生命周期内都存在,那么这些枚举器将有效地成为永久性内存泄漏。

    如果集合持有对枚举数的弱引用,这个问题可以在很大程度上得到解决。如果枚举器被废弃,它将有资格进行垃圾收集,即使收集仍然持有对它的弱引用。下次更改集合时,它可以查看其弱引用列表,将更新发送到仍然有效的引用,并从其列表中删除无效引用。

    使用终结器和一些额外的对象可以实现弱引用的许多效果,并且可以使这样的实现比使用弱引用的实现更有效,但是有很多陷阱并且很难避免错误.使用 Wea​​kReference 更容易做出正确的方法。该方法可能不是最有效的,但不会严重失败。

    【讨论】:

      【解决方案7】:

      Weak Pointers 阻止任何阻碍它们成为指针指向的对象的“生命支持”形式。

      假设您有一个 Viewport 类、两个 UI 类和一堆 Widget 类。您希望您的 UI 控制它创建的 Widget 的生命周期,因此您的 UI 将 SharedPtrs 保留给它控制的所有 Widget。只要您的 UI 对象还活着,它所引用的任何 Widget 都不会被垃圾回收(感谢 SharedPtr)。

      但是,Viewport 是您实际执行绘图的类,因此您的 UI 需要向 Viewport 传递一个指向 Widget 的指针,以便它可以绘制它们。无论出于何种原因,您都希望将活动 UI 类更改为另一个。让我们考虑两种情况,一种是 UI 通过 Viewport WeakPtrs,另一种是通过 SharedPtrs(指向 Widget)。

      如果您已将所有 Widget 作为 WeakPointers 传递给 Viewport,一旦 UI 类被删除,Widget 将不再有 SharedPointers,因此它们将被垃圾收集,Viewport 对对象的引用将不会保留他们在“生命支持”上,这正是你想要的,因为你甚至不再使用那个 UI,更不用说它创建的小部件了。

      现在,考虑到您已向 Viewport 传递了一个 SharedPointer,您删除了 UI,并且 Widget 不会被垃圾回收!为什么?因为仍然存在的视口有一个数组(向量或列表,等等),其中充满了到小部件的 SharedPtrs。视口实际上已成为他们“生命支持”的一种形式,即使您删除了控制另一个 UI 对象的小部件的 UI。

      通常,语言/系统/框架会对任何内容进行垃圾收集,除非内存中某处存在对它的“强”引用。想象一下,如果所有内容都对所有内容都具有强引用,那么任何东西都不会被垃圾收集!有时你想要那种行为,有时你不想要。如果你使用 Wea​​kPtr,并且没有 Shared/StrongPtrs 指向对象(只有 WeakPtrs),那么尽管有 WeakPtr 引用,对象仍将被垃圾收集,并且 WeakPtrs(应该)设置为 NULL(或删除,或东西)。

      同样,当您使用 Wea​​kPtr 时,您基本上允许您提供给它的对象也能够访问数据,但是 WeakPtr 不会像 SharedPtr 一样阻止它指向的对象的垃圾收集.当你想到 SharedPtr 时,就想到“生命支持”,WeakPtr,没有“生命支持”。在对象的生命支持为零之前(通常)不会发生垃圾收集。

      【讨论】:

        【解决方案8】:

        弱引用例如可以用在缓存场景中——你可以通过弱引用访问数据,但是如果你长时间不访问数据或者内存压力大,GC可以释放它。

        【讨论】:

        • -1:同样,这个例子正是你应该使用弱指针的时候。
        • 我不明白...为什么这得到了反对,而上面完全相同的答案得到了 7 个赞成。
        【解决方案9】:

        垃圾收集的根本原因在于,在像 C 这样的语言中,内存管理完全在程序员的显式控制之下,当对象所有权被传递时,尤其是在线程之间,或者更困难的是,在共享内存的进程之间传递,避免内存泄漏和悬空指针会变得非常困难。如果这还不够难,您还必须处理一次访问的对象数量超过内存容量的需求——您需要有一种方法可以暂时释放一些对象,以便其他对象可以在内存中。

        因此,某些语言(例如 Perl、Lisp、Java)提供了一种机制,您可以在其中停止“使用”对象,垃圾收集器最终会发现这一点并释放用于对象的内存。它可以正确地做到这一点,而程序员不必担心他们可能会出错的所有方式(尽管程序员可以通过多种方式搞砸)。

        如果您从概念上将访问对象的次数乘以计算对象值所需的时间,并可能再次乘以对象不可用的成本或对象的大小由于在内存中保留一个大对象可以防止保留几个较小的对象,因此您可以将对象分为三类。

        有些对象非常重要,以至于您想显式管理它们的存在——它们不会由垃圾收集器管理,或者在显式释放之前永远不能收集它们。有些对象计算成本低、体积小、访问不频繁或具有相似的特性,可以随时进行垃圾回收。

        第三类,重新计算代价高昂但可以重新计算的对象,访问频率较高(可能是短时间),尺寸较大,等等属于第三类。您希望将它们尽可能长时间地保存在内存中,因为它们可能会再次被重用,但您不想耗尽关键对象所需的内存。这些是弱引用的候选对象。

        如果这些对象不与关键资源冲突,您希望它们尽可能长时间地保留,但如果关键资源需要内存,则应该删除它们,因为它可以在需要时重新计算。这些是弱指针的用途。

        图片就是一个例子。假设您有一个包含数千张图片的照片网页要显示。您需要知道要布置多少张图片,也许您必须执行数据库查询才能获取列表。保存数千个项目的列表的内存可能非常小。您希望查询一次并保留它。

        但是,您一次只能在网页的窗格中实际显示几十张图片。您不需要为用户无法查看的图片获取位。当用户滚动页面时,您将收集可见图片的实际位。这些图片可能需要很多兆字节才能显示出来。如果用户在几个滚动位置之间来回滚动,您不想一遍又一遍地重新获取这些兆字节。但是你不能一直把所有的图片都保存在内存中。所以你使用弱指针。

        如果用户只是一遍又一遍地查看几张图片,它们可能会保留在缓存中,而您不必重新获取它们。但是如果它们滚动得足够多,您需要释放一些内存以便可以获取可见图片。使用弱引用,您在使用它之前检查引用。如果它仍然有效,则使用它。如果不是,您需要进行昂贵的计算(获取)来获取它。

        【讨论】:

        • 只是一个帮助您提供更好答案的观察:问题是“为什么弱指针有用?”而不是“什么是缓存以及弱指针如何提供帮助?”。尽量保持重点,简洁,如果问题需要详细的答案,请利用格式标记使您的答案更易于阅读。
        • -1:这个例子正是你应该使用弱指针的时候。 GC 不会泄漏,直到在收集之前消耗完所有资源。他们非常积极地收集。如果您使用 GC 驱逐它们,您的缓存行可能不会持续 1 毫秒,因为它们甚至无法在下一个 gen0 集合中存活。
        猜你喜欢
        • 2019-08-25
        • 2013-03-07
        • 1970-01-01
        • 1970-01-01
        • 2014-10-22
        • 2022-01-02
        • 2023-03-12
        • 1970-01-01
        • 2014-05-13
        相关资源
        最近更新 更多