为什么Java和Python垃圾收集方法有所不同?
Python使用引用计数方法来处理对象的生命周期。 所以没有更多使用的对象将被立即销毁。
但是,在Java中,GC(垃圾回收器)会破坏在特定时间不再使用的对象。
为什么Javaselect这个策略,从中得到什么好处呢?
这比Python方法更好吗?
使用引用计数有一些缺点。 其中提到的最多的是循环引用:假设引用B,B引用C和C引用B.如果A将它的引用放到B,那么B和C将仍然有一个引用计数为1,并且不会被删除与传统的参考计数。 CPython(引用计数不是Python本身的一部分,但是它的C实现的一部分)通过一个单独的垃圾回收例程捕获循环引用,它周期性地运行…
另一个缺点:引用计数会使执行速度变慢。 每次一个对象被引用和取消引用时,解释器/虚拟机必须检查计数是否已经降到0(然后如果是,则释放)。 垃圾收集不需要这样做。
此外,垃圾收集可以在一个单独的线程完成(虽然它可能有点棘手)。 在有很多内存的机器上,以及只能缓慢使用内存的进程中,你可能根本不想做GC! 引用计数在性能方面会有一些缺点…
实际上引用计数和Sun JVM使用的策略都是不同types的垃圾收集algorithm。
追踪死对象有两种广泛的方法:追踪和引用计数。 跟踪GC从“根”开始 – 像堆栈引用之类的东西,并跟踪所有可访问(活动)对象。 任何不能达到的东西都被认为是死的。 在每次引用被修改的引用计数中,所涉及的对象的计数已经被更新。 引用计数设置为零的任何对象都被认为是死的。
基本上所有的GC实现都存在权衡,但是跟踪通常对高通(即快速)操作有好处,但具有较长的暂停时间(UI或程序可能冻结的较大差距)。 引用计数可以在较小的块中运行,但总体上会较慢。 这可能意味着冻结较less,但整体performance较差。
另外,引用计数GC需要周期检测器来清除周期中的任何对象,这些对象不会被引用计数单独捕获。 Perl 5在其GC实现中没有循环检测器,可能会泄漏循环内存。
为了获得两全其美(暂停时间短,吞吐量高)的研究: http : //cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf
达伦·托马斯给出了很好的答案。 然而,Java和Python方法之间的一个很大的区别在于,在通常情况下(无循环引用)通过引用计数来立即清除对象,而不是在稍后的某个不确定的date。
例如,我可以在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风格的世代垃圾收集器,可以在单独的线程中运行,并具有所有这些疯狂的优化等等。写你的代码应该是微不足道的,理想情况下是不存在的。
我认为IBM的文章“ Java理论与实践:垃圾收集的简史 ”应该有助于解释一些问题。
如果您有足够的内存,垃圾收集比参考计数更快(更省时)。 例如,复制gc遍历“活”对象并将它们复制到一个新的空间,并且可以通过标记整个内存区域来一步回收所有“死”对象。 如果你有足够的内存,这是非常有效的。 世代collections使用“大多数物体年轻化”的知识; 通常只有百分之几的对象必须被复制。
[这也是gc可以比malloc / free更快的原因]
引用计数比垃圾收集更节省空间,因为它在无法访问的时刻回收内存。 这是很好的,当你想附加终结器的对象(例如,一旦File对象无法访问closures文件)。 即使只有百分之几的内存空闲,引用计数系统也可以工作。 但是在每个指针分配上增加和减less计数器的pipe理成本花费了大量时间,并且还需要某种垃圾收集来回收周期。
因此,权衡是明确的:如果您必须在内存受限的环境中工作,或者如果您需要精确的终结器,请使用引用计数。 如果你有足够的内存和需要的速度,使用垃圾收集。
Java跟踪GC的一大缺点是,它会不时地“停止世界”,并冻结相对较长的时间来完成GC。 如果堆大,对象树复杂,则会冻结几秒钟。 另外每个完整的GC都会一遍又一遍地访问整个对象树,可能效率很低。 Java执行GC的另一个缺点是你必须告诉jvm你想要的堆大小(如果默认值不够好); JVM从该值派生出几个阈值,当堆中垃圾堆积太多时,这些阈值将触发GC进程。
我认为这实际上是Android(基于Java),即使是在最昂贵的手机上,与iOS(基于ObjectiveC,使用RC)的stream畅性相比,这种不平顺的感觉的主要原因。
我很希望看到一个jvm选项来启用RC内存pipe理,也许只有在没有剩余内存的情况下,GC才能作为最后的手段运行。
最新的Sun Java VM实际上有多个你可以调整的GCalgorithm。 Java VM规范故意忽略了指定实际的GC行为,以允许针对不同VM的不同(和多个)GCalgorithm。
例如,对于所有不喜欢默认Sun Java VM GC行为的“停止世界”方法的人来说,都有像IBM的WebSphere Real Time这样的VM,它允许实时应用程序在Java上运行。
由于Java VM规范是公开可用的,所以从理论上讲没有任何人阻止任何人使用CPython的GCalgorithm来实现Java VM。
引用计数在multithreading环境中特别难以有效地执行。 我不知道如何进行硬件辅助交易或类似的(目前)不寻常的primefaces指令。
引用计数很容易实现。 JVM有大量资金投入竞争实施,所以他们实施非常好的解决scheme来解决非常棘手的问题并不奇怪。 但是,在JVM上定位自己喜欢的语言变得越来越容易了。
在游戏后期,但我认为在Python中RC的一个重要的基本原理是它的简单性。 例如,请参阅Alex Martelli的电子邮件 。
(我无法find一个链接外的谷歌caching,电子邮件date从2005年10月13日在Python列表)。