15

我一直在阅读垃圾收集,寻找包含在我的编程语言中的功能,我遇到了“弱指针”。从这里

弱指针类似于指针,只是来自弱指针的引用不会阻止垃圾回收,并且弱指针在使用之前必须检查其有效性。

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

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

4

9 回答 9

9

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

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

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

于 2009-09-28T03:40:22.983 回答
8

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

于 2009-09-28T03:44:23.380 回答
5

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

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

于 2009-09-28T03:54:24.437 回答
3

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

于 2009-09-28T03:44:47.733 回答
1

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

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

于 2009-09-28T03:43:15.473 回答
0

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

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

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

如果您已将所有 Widgets 作为 WeakPointers 传递给 Viewport,则一旦 UI 类被删除,就不会再有 SharedPointers 指向 Widgets,因此它们将被垃圾收集,Viewport 对对象的引用不会使它们保持打开状态“生命支持”,这正是您想要的,因为您甚至不再使用该 UI,更不用说它创建的小部件了。

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

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

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

于 2014-03-31T03:09:45.637 回答
0

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

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

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

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

于 2010-11-18T00:30:56.637 回答
-1

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

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

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

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

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

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

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

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

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

于 2009-09-28T05:03:59.460 回答
-1

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

于 2009-09-28T03:42:29.857 回答