为什么Java和Python垃圾收集方法不同?

时间:2008-08-22 07:35:27

标签: java python garbage-collection

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

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

为什么Java会选择这种策略,这会带来什么好处?

这比Python方法更好吗?

9 个答案:

答案 0 :(得分:44)

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

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

此外,垃圾收集可以在一个单独的线程中完成(虽然它可能有点棘手)。在具有大量RAM的机器上以及仅缓慢使用内存的进程中,您可能根本不想进行GC操作!就性能而言,引用计数会有一点缺点......

答案 1 :(得分:26)

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

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

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

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

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

答案 2 :(得分:13)

达伦托马斯给出了一个很好的答案。但是,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风格的分代垃圾收集器,它可以在一个单独的线程中运行,并具有所有这些疯狂的优化等等。如何编写代码应该可以忽略不计,理想情况下不存在。

答案 3 :(得分:8)

我认为IBM的文章“Java theory and practice: A brief history of garbage collection”应该有助于解释您的一些问题。

答案 4 :(得分:5)

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

[这也是gc比malloc / free更快的原因]

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

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

答案 5 :(得分:3)

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

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

我很想看到一个启用RC内存管理的jvm选项,并且可能只保留GC,以便在没有剩余内存时作为最后的手段运行。

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:2)

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

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

答案 8 :(得分:0)

游戏后期,但我认为在python中RC的一个重要原因是它的简单性。例如,请参阅此email by Alex Martelli

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