新的自动引用计数机制是如何工作的?

Posted

技术标签:

【中文标题】新的自动引用计数机制是如何工作的?【英文标题】:How does the new automatic reference counting mechanism work? 【发布时间】:2011-09-17 03:09:49 【问题描述】:

有人可以向我简要解释一下 ARC 的工作原理吗?我知道它与垃圾收集不同,但我只是想知道它是如何工作的。

另外,如果 ARC 在不影响性能的情况下完成 GC 的工作,那么 Java 为什么要使用 GC?为什么不使用 ARC?

【问题讨论】:

这会告诉你一切:http://clang.llvm.org/docs/AutomaticReferenceCounting.html它是如何在 Xcode 和 ios 5 中实现的。 @mbehan 这是个糟糕的建议。我不想登录,甚至不想拥有 iOS 开发中心的帐户,但我仍然有兴趣了解 ARC。 ARC 并没有做 GC 所做的所有事情,它要求您明确地使用强引用语义和弱引用语义,如果您没有正确处理这些语义,则会泄漏内存。根据我的经验,当您在 Objective-C 中使用块时,这一开始很棘手,即使在您了解了这些技巧之后,您也会留下一些关于块的许多用法的烦人 (IMO) 样板代码.忘记强/弱引用更方便。此外,GC 可以比 ARC wrt 执行得更好一些。 CPU,但需要更多内存。当您拥有大量内存时,它可能比显式内存管理更快。 @TaylanUB:“需要更多内存”。很多人都这么说,但我觉得很难相信。 @JonHarrop:说实话,目前我什至不记得我为什么这么说。 :-) 与此同时,我意识到有这么多不同的 GC 策略,以至于这些笼统的陈述可能都毫无价值。让我背诵 Hans Boehm 的 内存分配神话和半真半假“为什么这个地区如此容易出现可疑的民间智慧?” 【参考方案1】:

ARC 是一种编译器功能,可提供对象的自动内存管理。

您不必记住何时使用retain, releaseautorelease,ARC 会评估对象的生命周期要求并在编译时自动为您插入适当的内存管理调用。编译器还会为您生成适当的 dealloc 方法。

编译器在编译时插入必要的retain/release 调用,但这些调用在运行时执行,就像任何其他代码一样。

下图将使您更好地了解 ARC 的工作原理。

那些刚接触 iOS 开发并且没有 Objective C 工作经验的人。 请参阅 Apple 的 Advanced Memory Management Programming Guide 文档以更好地了解内存管理。

【讨论】:

【参考方案2】:

Apple 开发人员文档对此进行了很好的解释。阅读"How ARC Works"

为确保实例在仍然需要时不会消失,ARC 会跟踪当前引用每个类实例的属性、常量和变量的数量。只要至少一个对该实例的活动引用仍然存在,ARC 就不会释放该实例。

为确保实例在仍然需要时不会消失,ARC 会跟踪当前引用每个类实例的属性、常量和变量的数量。只要至少一个对该实例的活动引用仍然存在,ARC 就不会释放该实例。

了解差异。垃圾收集和 ARC 之间的关系:阅读this

【讨论】:

【参考方案3】:

ARC 只是播放旧的保留/释放 (MRC),编译器确定何时调用保留/释放。与 GC 系统相比,它往往具有更高的性能、更低的峰值内存使用和更可预测的性能。

另一方面,ARC(或 MRC)无法处理某些类型的数据结构,而 GC 可以处理它们。

举个例子,如果你有一个名为 node 的类,并且 node 有一个 NSArray 的子节点,以及一个对它的父节点的单一引用,它与 GC 一起“正常工作”。使用 ARC(以及手动引用计数)你就有问题了。任何给定节点都将被其子节点以及其父节点引用。

喜欢:

A -> [B1, B2, B3]
B1 -> A, B2 -> A, B3 -> A

使用 A 时一切正常(例如通过局部变量)。

当您完成它(以及 B1/B2/B3)后,GC 系统最终会决定从堆栈和 CPU 寄存器开始查看它可以找到的所有内容。它永远不会找到 A、B1、B2、B3,因此它将最终确定它们并将内存回收到其他对象中。

当您使用 ARC 或 MRC 并以 A 结束时,它的引用计数为 3(B1、B2 和 B3 都引用它),并且 B1/B2/B3 的引用计数都将为 1(A 的 NSArray 持有每个参考)。因此,即使没有任何东西可以使用它们,所有这些对象仍然存在。

常见的解决方案是确定其中一个引用需要是弱引用(不影响引用计数)。这适用于某些使用模式,例如,如果您仅通过 A 引用 B1/B2/B3。但是在其他模式中它会失败。例如,如果您有时会握住 B1,并希望通过父指针向上爬并找到 A。如果您只握住 B1,则使用弱引用,A 可以(并且通常会)蒸发,并拿走 B2 和 B3用它。

有时这不是问题,但一些非常有用且自然的处理复杂数据结构的方法很难与 ARC/MRC 一起使用。

所以 ARC 的目标与 GC 目标相同。然而 ARC 的使用模式比 GC 更有限,所以如果你采用 GC 语言(如 Java)并将 ARC 之类的东西移植到它上面,一些程序将不再工作(或者至少会产生大量废弃的内存,并可能导致严重的交换问题或内存或交换空间不足)。

您也可以说 ARC 更重视性能(或者可能是可预测性),而 GC 更重视成为通用解决方案。因此,GC 的 CPU/内存需求可预测性较低,性能(通常)低于 ARC,但可以处理任何使用模式。 ARC 对于许多常见的使用模式会更好地工作,但对于一些(有效的!)使用模式,它会崩溃并死掉。

【讨论】:

“另一方面,ARC 无法实现某些类型的数据结构”我认为您的意思是没有提示就无法进行自动清理;显然,数据结构是。 当然,但是在 ARC 下只能自动清理 ObjC 对象,因此“无自动清理”==“无清理”。不过,等我有更多时间我会改写然后回答。 @Stripes:ARC 中手动清理的等价物是手动打破循环,例如foo = nil “[ARC] 往往具有更高的性能...ARC 将更高的优先级放在性能上”。当众所周知引用计数比跟踪垃圾收集慢得多时,我很惊讶。 flyingfrogblog.blogspot.co.uk/2011/01/… 理论上 GC 更快(每个引用计数操作必须是多处理器缓存一致的,而且有很多)。实际上,ObjC 唯一可用的 GC 系统要慢得多。 GC 系统在用户可感知的时间内随机暂停线程也是非常常见的(有一些实时 GC 系统,但它们并不常见,我认为它们有“有趣”的约束)【参考方案4】:

每个接触 Objective-C 的新开发人员都必须了解何时保留、释放和自动释放对象的严格规则。这些规则甚至指定了暗示从方法返回的对象的保留计数的命名约定。一旦您牢记这些规则并始终如一地应用它们,Objective-C 中的内存管理就会成为第二天性,但即使是最有经验的 Cocoa 开发人员也会时常犯错。

借助 Clang 静态分析器,LLVM 开发人员意识到这些规则足够可靠,他们可以构建一个工具来指出代码所采用路径中的内存泄漏和过度释放。

Automatic reference counting (ARC) 是下一个合乎逻辑的步骤。如果编译器可以识别您应该在哪里保留和释放对象,为什么不让它为您插入该代码呢?刚性、重复性的任务是编译器和他们的兄弟们擅长的。人类会忘记事情并犯错误,但计算机的一致性要好得多。

但是,这并不能完全让您不必担心这些平台上的内存管理。我在回答here 中描述了需要注意的主要问题(保留周期),这可能需要您稍微考虑一下以标记弱指针。但是,与您在 ARC 中获得的相比,这微不足道。

与手动内存管理和垃圾收集相比,ARC 为您提供了两全其美的优势,它无需编写保留/释放代码,而且没有在垃圾收集环境中看到的暂停和锯齿状内存配置文件。垃圾回收的唯一优势在于它处理保留周期的能力以及原子属性分配成本低廉的事实(如here 所讨论的)。我知道我正在用 ARC 实现替换所有现有的 Mac GC 代码。

至于这是否可以扩展到其他语言,它似乎与 Objective-C 中的引用计数系统有关。将其应用于 Java 或其他语言可能很困难,但我对低级编译器细节的了解还不够,无法在那里做出明确的陈述。鉴于 Apple 是在 LLVM 中推动这一努力的人,Objective-C 将首先出现,除非另一方为此投入大量自己的资源。

这个震惊的开发者在 WWDC 上的揭幕,所以人们不知道可以做这样的事情。随着时间的推移,它可能会出现在其他平台上,但目前它是 LLVM 和 Objective-C 独有的。

【讨论】:

强调我的:这并不能完全让您不必担心内存管理 ARC 真的是一项创新吗?从您的回答中,我得出结论,ARC 是一个新概念,首次在 Objective-C 中使用(如果我错了,请纠正我)。老实说,我不是 Objective-C 开发人员,对 ARC 了解不多,但是 Boost Shared Pointers(参见 boost.org)不完全一样吗?如果不是,有什么区别? @DMM - 这是一个编译器级别的过程,而不是依赖于重载的运算符(如 Boost 所做的那样),它将其扩展到整个语言。除此之外,这使得将手动引用计数的应用程序转换为 ARC 变得很容易。 Boost 也可能以不同于 ARC 的方式处理局部变量,ARC 知道局部变量不再被使用的时刻,并且可以在该点释放。我相信使用 Boost,你仍然需要以某种方式指定你已经完成了变量。 为了回答“它是新的”问题,十多年来,Delphi 已经对字符串、数组和接口(用于 COM 支持)进行自动引用计数。我同意这确实是 gc'd 环境和“手动完成”环境之间的一个很好的折衷方案。我很高兴它在 ObjC 和 LLVM 中(因此其他语言也可以利用它)。 @theDmi:“ARC 真的是一项创新吗?”。自动引用计数发明于 1960 年,并已在 Python 和 Mathematica 等多种语言中使用。它没有在 JVM 或 CLR 中使用,因为它非常慢并且会泄漏周期。【参考方案5】:

魔法

但更具体地说,ARC 的工作原理与您对代码所做的完全一样(有一些细微差别)。 ARC 是一种编译时技术,与运行时的 GC 不同,它会对您的性能产​​生负面影响。 ARC 会为你跟踪对对象的引用,并按照常规规则合成retain/release/autorelease 方法。因为这个 ARC 也可以在不再需要时立即释放它们,而不是纯粹为了约定而将它们扔到自动释放池中。

其他一些改进包括清零弱引用、自动将块复制到堆、全面加速(自动释放池为 6 倍!)。

有关所有这些工作原理的更详细讨论,请参见 ARC 上的 LLVM Docs。

【讨论】:

-1 "ARC 是一种编译时技术,与运行时的 GC 不同,它会对您的性能产​​生负面影响"。引用计数在运行时增加,这是非常低效的。这就是跟踪 JVM 和 .NET 等 GC 的速度要快得多的原因。 @Jon:你有证据吗?从我自己的阅读来看,似乎新的 RC 算法的性能通常与 M&S GC 一样好或更好。 @xryl669:GC 手册中有完整的解释 (gchandbook.org)。请注意,追踪!= M&S。【参考方案6】:

它与垃圾收集有很大不同。您是否看到警告您可能在不同的线路上泄漏对象?这些语句甚至告诉您在哪一行分配了对象。这更进了一步,现在可以在正确的位置插入retain/release 语句,比大多数程序员更好,几乎 100% 的时间。偶尔会有一些奇怪的保留对象实例需要您帮助解决。

【讨论】:

以上是关于新的自动引用计数机制是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

自动引用计数ARC

Python如何进行内存管理

详解Python垃圾回收机制

Python 的内存管理机制

unity啥情况下gc不能自动回收垃圾对象

[OC学习笔记]自动引用计数