Objective-C 自动引用计数和垃圾回收有啥区别?

Posted

技术标签:

【中文标题】Objective-C 自动引用计数和垃圾回收有啥区别?【英文标题】:What is the difference between Objective-C automatic reference counting and garbage collection?Objective-C 自动引用计数和垃圾回收有什么区别? 【发布时间】:2011-12-14 00:45:52 【问题描述】:

随着 Xcode 4.2 中引入的新的自动引用计数 (ARC),我们不再需要在 Objective-C 中手动管理保留/释放。

这似乎类似于垃圾收集,就像在 Mac 上的 Objective-C 和其他语言中所做的那样。 ARC 与垃圾回收有何不同?

【问题讨论】:

另见why-java-and-python-garbage-collection-methods-are-different 【参考方案1】:

ARC 依赖于编译时“引用”对象,这使其在低功耗模式环境(移动设备)中高效。

GC 依赖于基于运行时的“可达”对象,这使其在多线程环境中高效。

操作

ARC 将代码注入到可执行文件中,以根据其引用计数“自动”在未使用的对象上执行。

GC 在运行时工作,因为它会检测未使用的对象图(将消除保留周期)并在不确定的时间间隔内将其删除

自动引用计数的优点

实时、确定性地销毁对象 未使用。 无后台处理。

垃圾回收的优点

GC 可以清理整个对象图,包括保留周期。 GC 在后台进行,因此完成的内存管理工作更少 作为常规申请流程的一部分。

自动引用计数的缺点

ARC 无法自动处理保留周期。

垃圾回收的缺点

由于 GC 发生在后台,对象的确切时间范围 版本未定。 发生 GC 时,应用程序中的其他线程可能 暂时搁置。

【讨论】:

【参考方案2】:

正如我在回答 here 中所描述的,ARC 可以提供最好的手动内存管理和跟踪垃圾收集。它主要消除了开发人员在 Objective-C 对象上跟踪手动保留、释放和自动释放的需要,同时避免了垃圾收集器进程的需要,该进程会耗尽移动设备上的有限资源并导致正在运行的应用程序偶尔出现卡顿.

ARC 通过应用所有 Objective-C 开发人员多年来必须使用的规则,在编译时插入引用计数所需的适当保留和释放。这使开发人员不必自己管理它。因为保留和释放是在编译时插入的,所以不需要收集器进程来不断地清理内存并删除未引用的对象。

与 ARC 相比,跟踪垃圾收集的一个小优势是 ARC will not deal with retain cycles for you,跟踪垃圾收集可以在其中拾取这些。

this thread on Apple's Objective-C mailing list 对此主题进行了精彩的阅读,其中 Chris Lattner 是这样说的:

GC 优于 ARC 的主要优点是它收集保留 循环。第二个优点是“保留”分配是 “原子”,因为它们是一个简单的存储。 ARC有几大 与 libauto GC 相比的优势:

    它具有确定性的对象回收(当对该对象的最后一个强引用消失时),其中 GC 有时会释放一个对象 稍后”。这定义了可能存在于 GC 中的一类细微错误 由于收集器未触发“在 错误的窗口”。 ARC 的高水位线通常比 GC 低得多,因为对象释放得更快。 libauto 提供了一个脆弱的编程模型,你必须小心不要丢失写屏障等。 并非所有的系统框架都是 GC 干净的,而且这些框架偶尔会随着它们的发展而退化。 ARC 不受假根的影响。 libauto 保守地扫描堆栈,这意味着看起来像指针的整数可以根 对象图。 ARC 没有任何东西可以启动和停止您的应用程序,从而导致 UI 卡顿。就 GC 实现而言,libauto 相当先进 go 因为它不会立即停止每个线程,但它仍然会 通常最终会停止所有 UI 线程。

我目前正在将我的手动内存管理项目以及使用 Objective-C 垃圾收集的项目迁移到 ARC。在几个 Mac 应用程序中使用垃圾收集一段时间后,我发现将这些项目迁移到 ARC 有一些显着优势。

【讨论】:

ARC 相对于 libauto 的另一个优势是不会减少虚拟地址空间。见***.com/a/5522746/104790 我要强调的是,Chris Lattner 专门讨论的是 libauto,而不是一般的跟踪垃圾收集。我还要指出 ARC 是一种垃圾收集形式,因此谈论“ARC vs GC”是没有意义的。您的真正意思是“ARC 与跟踪 GC”。 @Asik - 也许我的措辞不准确。我已经改进了上面的内容以表明我的意思,因为引用计数所需的保留和释放是在编译时插入的。当然,引用计数本身发生在运行时,但关键是这避免了跟踪垃圾收集所需的收集器进程。这个问题的上下文是关于 Objective-C (libauto) 垃圾收集,所以我假设阅读这篇文章的人会理解“垃圾收集”指的是跟踪垃圾收集,带有收集器进程。 @Asik - 就口吃而言,ARC 在对象和对象图被释放的位置是确定性的。这与对象在不再需要时被释放的事实相结合,这意味着在实践中,与以特定间隔扫描对象图的收集器进程相比,您看到的卡顿要少得多。此外,它是确定性的,因此可以轻松识别解除分配导致故障或暂停的点,并重新进行适当的操作以避免或显着减少这种情况。 你是说循环收集是一个“轻微”的优势?尝试在代码中更复杂地使用块(ObjC 闭包)。 :-) 另一个区别是跟踪的 CPU 效率,它不需要到处乱扔原子引用计数操作的代码。【参考方案3】:

ARC 与垃圾回收有何不同?

ARC 是垃圾回收的一种形式。

您可能的意思是“ARC 和跟踪垃圾收集(如 JVM 和 .NET)有什么区别?”。主要区别在于 ARC 速度较慢且泄漏周期。这就是 JVM 和 .NET 都使用跟踪垃圾收集器的原因。更多信息请阅读How do reference counting and tracing garbage collection compare?。

【讨论】:

我知道引用计数可能会泄漏周期,但它怎么会变慢呢?一旦 RC 达到零,就可以释放资源。没有人知道在跟踪 GC 中释放资源需要多长时间,这里有什么不正确的地方吗? @hylepo:这是正确的,经常被引用,但具有很大的误导性。 “一旦 RC 达到零”但计数何时达到零?递减几乎总是推迟到作用域的末尾,因此无论是否需要对象,它们都保持活动到作用域的末尾。因此,与流行的看法相反,RC 最早不会收集。 “怎么能慢?”。引用计数很慢,因为它会产生大量的计数器增量和减量,这些计数器通常是非本地的,因此对缓存不友好。在 inc/dec 必须是原子的多线程上下文中更糟糕。 @hylepo:这些都是已解决的问题,但 所有 解决方案都牺牲了您提到的好处。例如,您可以推迟递减以摊销成本,但 RC 就像跟踪 GC 一样不可预测。另请注意,RC 仅对单线程程序是可预测的。一旦你有了多线程,你就可以进行递减,并且你无法预测哪个线程会在清理时停止。【参考方案4】:

简短而甜蜜的答案如下:

java的GC是Runtime,ARC是compile time。

GC 在运行时引用对象并检查对象运行时的依赖关系。 而 ARC 在编译时附加 release、retain、autorelease 调用。

【讨论】:

-1 这显然不是真的。 ARC 在运行时增加引用计数,并且仅在它们达到零时才释放。此外,ARC 是垃圾收集的一种形式,因此您的真正意思是“跟踪垃圾收集与引用计数垃圾收集”。 哇,这是流行神话如何自我维持的一个例子;这被投票通过并被接受真是太疯狂了。正如 Jon Harrop 所说,这个答案是完全错误的:引用计数是一种 run-time 机制。 正如其他人所指出的,这个答案是完全错误的。 GC 可能是最轻浮的神话和误解的话题之一。让一些伪知识分子散布熟透的谎言也无济于事。 taylanub.github.io/webapps-js-gc 这个答案是正确的:发布和保留调用在编译时添加的。是的,显然,实际的递增/递减发生在运行时,但调用是在运行时插入的。 自动引用计数 (ARC) 是编译时功能。具体来说,ARC 在编译时使用 LLVM 自动注入 Obective-C 运行时等效项,用于保留、释放和自动释放。如果没有 ARC 或 GC,您将需要手动添加调用以保留和释放,这将是手动引用计数 (MRC)。在后者的情况下,人类在开发时将调用添加到源代码中,而不是在运行时或编译时。不应将添加这些调用的策略与引用计数本身的策略混淆,后者确实发生在运行时。

以上是关于Objective-C 自动引用计数和垃圾回收有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

JVM的内存区域划分以及垃圾回收机制详解

垃圾回收机制

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

Python 垃圾回收

六种主要的垃圾回收算法和思想

垃圾回收机制