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

pop*_*ome 51 python java garbage-collection

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

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

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

这比Python方法更好吗?

Dar*_*mas 46

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

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

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

  • 值得注意的另一个区别是,通过引用计数的急切GC始终使用"最小"内存(循环依赖性情况除外),而Java的惰性方法可能会导致JVM暂时使用比实际需要更多的内存,直到GC运行带来它重新排成一行.Java的方法以内存为代价提供速度,并且在内存充足时具有优势.当它很稀缺时,Python的方法会更好. (18认同)

Luk*_*ane 27

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

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

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

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

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


Eli*_*ght 15

达伦托马斯给出了一个很好的答案.但是,Java和Python方法之间的一个重要区别是,在常见情况下(没有循环引用)引用计数对象会立即清除,而不是在某些不确定的日期.

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

def parse_some_attrs(fname):
    return open(fname).read().split("~~~")[2:4]
Run Code Online (Sandbox Code Playgroud)

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

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

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

但有时人们喜欢依赖引用计数来永远释放资源,因为它有时会使你的代码变短.

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

  • 好答案.对我来说,你的答案中的一个主要观点是在第二个例子中没有使用显式的`close()`.在python中编写的代码要少得多.这在http://docs.python.org/howto/doanddont.html上解释(搜索"with open") (4认同)

Esp*_*spo 8

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


小智 6

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

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

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


mfx*_*mfx 5

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

[这也是为什么gc可以比malloc/free更快的原因]

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

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