62

Python 使用引用计数方法来处理对象的生命周期。因此,不再使用的对象将立即被销毁。

但是,在 Java 中,GC(垃圾收集器)会销毁在特定时间不再使用的对象。

Java 为什么选择这种策略,这样做有什么好处?

这比 Python 方法好吗?

4

9 回答 9

51

使用引用计数有一些缺点。最常提到的一种是循环引用:假设 A 引用 B,B 引用 C 和 C 引用 B。如果 A 放弃对 B 的引用,则 B 和 C 的引用计数仍然为 1,并且不会被删除与传统的引用计数。CPython(引用计数不是 python 本身的一部分,而是其 C 实现的一部分)使用它定期运行的单独的垃圾收集例程来捕获循环引用......

另一个缺点:引用计数会使执行速度变慢。每次引用和取消引用对象时,解释器/VM 必须检查计数是否已降至 0(如果是,则解除分配)。垃圾收集不需要这样做。

此外,垃圾收集可以在单独的线程中完成(尽管它可能有点棘手)。在具有大量 RAM 的机器上和仅缓慢使用内存的进程上,您可能根本不想进行 GC!引用计数在性能方面会有点缺点......

于 2008-08-22T09:10:06.943 回答
32

实际上引用计数和 Sun JVM 使用的策略都是不同类型的垃圾收集算法。

追踪死对象有两种广泛的方法:跟踪和引用计数。在跟踪 GC 从“根”开始 - 诸如堆栈引用之类的东西,并跟踪所有可到达(活动)对象。任何无法到达的东西都被认为是死的。在每次修改引用时的引用计数中,所涉及的对象的计数都会更新。任何引用计数设置为零的对象都被认为是死的。

基本上所有的 GC 实现都有权衡,但跟踪通常有利于高吞吐量(即快速)操作,但暂停时间较长(UI 或程序可能冻结的较大间隙)。引用计数可以在较小的块中运行,但总体上会更慢。这可能意味着更少的冻结,但整体性能更差。

此外,引用计数 GC 需要循环检测器来清理循环中不会被单独的引用计数捕获的任何对象。Perl 5 在其 GC 实现中没有循环检测器,并且可能会泄漏循环内存。

还进行了研究以获得两全其美(低暂停时间,高吞吐量): http ://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

于 2008-10-13T01:42:10.507 回答
16

达伦托马斯给出了一个很好的答案。然而,Java 和 Python 方法之间的一大区别是,在常见情况下(无循环引用)的引用计数会立即清理对象,而不是在某个不确定的稍后日期。

例如,我可以在 CPython 中编写草率的、不可移植的代码,例如

def parse_some_attrs(fname):
    return open(fname).read().split("~~~")[2:4]

并且我打开的那个文件的文件描述符将被立即清理,因为一旦对打开文件的引用消失,文件就会被垃圾收集并释放文件描述符。当然,如果我运行 Jython 或 IronPython 或者可能是 PyPy,那么垃圾收集器不一定会运行到很晚;可能我会先用完文件描述符,我的程序会崩溃。

所以你应该编写看起来像的代码

def parse_some_attrs(fname):
    with open(fname) as f:
        return f.read().split("~~~")[2:4]

但有时人们喜欢依靠引用计数来释放他们的资源,因为它有时会使你的代码更短一些。

我想说最好的垃圾收集器是性能最好的垃圾收集器,目前似乎是 Java 风格的分代垃圾收集器,可以在单独的线程中运行并具有所有这些疯狂的优化等。编写您的代码应该可以忽略不计,理想情况下不存在。

于 2008-08-22T12:40:03.460 回答
8

我认为 IBM 的文章“ Java 理论与实践:垃圾收集简史”应该有助于解释您的一些问题。

于 2008-08-22T07:40:12.557 回答
6

如果您有足够的内存,垃圾收集比引用计数更快(更省时)。例如,一个复制 gc 遍历“活”对象并将它们复制到一个新的空间,并且可以通过标记整个内存区域来一步回收所有“死”对象。如果您有足够的内存,这非常有效。分代集合使用“大多数对象年轻时死去”的知识;通常只需要复制百分之几的对象。

【这也是gc能比malloc/free更快的原因】

引用计数比垃圾回收更节省空间,因为它会在它无法访问的那一刻回收内存。当您想将终结器附加到对象时(例如,一旦文件对象无法访问就关闭文件),这很好。即使只有百分之几的内存可用,引用计数系统也可以工作。但是每次指针分配时必须增加和减少计数器的管理成本会花费大量时间,并且仍然需要某种垃圾收集来回收周期。

所以权衡是明确的:如果您必须在内存受限的环境中工作,或者如果您需要精确的终结器,请使用引用计数。如果您有足够的内存并且需要速度,请使用垃圾收集。

于 2008-09-16T16:38:56.500 回答
6

Java 的跟踪 GC 的一大缺点是它会时不时地“停止世界”,并在相对较长的时间内冻结应用程序以进行完整的 GC。如果堆很大并且对象树很复杂,它会冻结几秒钟。此外,每个完整的 GC 都会一遍又一遍地访问整个对象树,这可能是非常低效的。Java 进行 GC 的另一个缺点是你必须告诉 jvm 你想要什么堆大小(如果默认值不够好);JVM 从该值派生出几个阈值,当堆中堆积太多垃圾时,这些阈值将触发 GC 进程。

我想这实际上是导致 Android(基于 Java)感觉生涩的主要原因,即使在最昂贵的手机上,与 iOS(基于 ObjectiveC,使用 RC)的流畅性相比也是如此。

我很想看到一个 jvm 选项来启用 RC 内存管理,并且可能只在没有更多内存时才运行 GC 作为最后的手段。

于 2011-10-19T18:40:07.457 回答
3

最新的 Sun Java VM 实际上有多个可以调整的 GC 算法。Java VM 规范有意省略了对实际 GC 行为的指定,以允许针对不同 VM 使用不同(和多种)GC 算法。

例如,对于所有不喜欢默认 Sun Java VM GC 行为的“stop-the-world”方法的人来说,有一些 VM,例如IBM 的 WebSphere Real Time,它允许实时应用程序在 Java 上运行。

由于 Java VM 规范是公开可用的,因此(理论上)没有什么能阻止任何人实现使用 CPython 的 GC 算法的 Java VM。

于 2008-08-22T22:58:36.080 回答
3

引用计数在多线程环境中特别难以有效执行。我不知道如果不进入硬件辅助事务或类似(当前)不寻常的原子指令,你甚至会如何开始这样做。

引用计数很容易实现。JVM 已经在相互竞争的实现中投入了大量资金,因此它们为非常困难的问题实现了非常好的解决方案也就不足为奇了。但是,在 JVM 上定位您最喜欢的语言变得越来越容易。

于 2008-09-05T20:03:19.737 回答
0

在游戏后期,但我认为在 python 中使用 RC 的一个重要理由是它的简单性。例如,请参阅Alex Martelli 的这封电子邮件

(我在 google 缓存之外找不到链接,python 列表上的电子邮件日期为 2005 年 10 月 13 日)。

于 2009-10-22T01:11:46.693 回答