为什么Java和Python垃圾收集方法不同?
Python使用引用计数方法来处理对象生存期。因此,不再使用的对象将立即被销毁。
但是,在Java中,GC(垃圾收集器)会销毁在特定时间不再使用的对象。
Java为什么选择这种策略,这样做有什么好处?
这比Python方法更好吗?
解决方案
回答
我认为IBM的文章" Java理论与实践:垃圾收集的简要历史"应该有助于解释我们所遇到的一些问题。
回答
使用引用计数存在弊端。最常提及的一种是循环引用:假设A引用B,B引用C和C引用B。如果A将其对B的引用删除,则B和C仍将具有1的引用计数,并且不会被删除。与传统的参考计数。 CPython(引用计数不是python本身的一部分,而是其C实现的一部分)使用单独的垃圾收集例程捕获循环引用,该例程定期运行...
另一个缺点:引用计数会使执行速度变慢。每次引用和取消引用对象时,解释器/ VM都必须检查以查看计数是否已降至0(如果已减少,则进行释放)。垃圾回收不需要这样做。
另外,垃圾回收可以在单独的线程中完成(尽管可能有些棘手)。在具有大量RAM的计算机上以及对于仅缓慢使用内存的进程,我们可能根本不想做GC!就性能而言,引用计数可能会有一点缺点。
回答
达伦·托马斯(Darren Thomas)给出了一个很好的答案。但是,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风格的世代垃圾收集器。编写代码应该可以忽略不计,并且理想情况下不存在。
回答
最新的Sun Java VM实际上具有多种可以调整的GC算法。 Java VM规范有意省略了指定实际GC行为的过程,以允许不同VM使用不同(和多个)GC算法。
例如,对于所有不喜欢默认的Sun Java VM GC行为的"世界停止"方法的人们,都有诸如IBM的WebSphere Real Time这样的VM,它们允许实时应用程序在Java上运行。
由于Java VM规范是公开可用的,因此(理论上)没有任何事情可以阻止任何人实施使用CPython的GC算法的Java VM。
回答
在多线程环境中,引用计数特别难以有效地进行。我不知道如果不进行硬件辅助交易或者类似的(当前)不寻常的原子指令,我们怎么会开始这样做。
引用计数很容易实现。 JVM在竞争性的实现中投入了大量资金,因此它们为非常困难的问题实现了非常好的解决方案也就不足为奇了。但是,将我们喜欢的语言定位到JVM变得越来越容易。
回答
如果我们有足够的内存,则垃圾回收比引用计数更快(更节省时间)。例如,复制gc遍历"活动"对象并将它们复制到新空间,并且可以通过标记整个内存区域一步收回所有"死"对象。如果我们有足够的内存,这将非常有效。分代收藏使用"大多数对象都死于年轻"的知识;通常只需要复制百分之几的对象。
[这也是gc可以比malloc / free更快的原因]
引用计数比垃圾回收具有更高的空间效率,因为它在无法访问的那一刻就回收内存。当我们要将终结器添加到对象时(例如,一旦File对象不可访问时关闭文件),这很好。即使只有百分之几的内存可用,参考计数系统也可以工作。但是在每次分配指针时必须增加和减少计数器的管理成本要花费大量时间,并且仍然需要某种垃圾回收来回收周期。
因此,权衡很明显:如果必须在内存受限的环境中工作,或者需要精确的终结器,请使用引用计数。如果我们有足够的内存并需要速度,请使用垃圾回收。
回答
实际上,引用计数和Sun JVM使用的策略都是不同类型的垃圾收集算法。
跟踪死对象有两种广泛的方法:跟踪和引用计数。跟踪GC时要从"根"开始,例如堆栈引用,并跟踪所有可到达的(活动的)对象。任何无法达到的东西都被认为是死亡。每次修改参考时,在参考计数中,所涉及对象的计数都会更新。将其引用计数设置为零的任何对象都被视为无效对象。
对于基本上所有的GC实现,都需要权衡取舍,但是跟踪通常适合高吞吐量(即快速)操作,但具有较长的暂停时间(UI或者程序可能冻结的较大间隙)。引用计数可以以较小的块进行操作,但总体上会较慢。这可能意味着冻结较少,但总体性能较差。
另外,参考计数GC需要一个循环检测器来清除循环中的所有对象,而这些对象不会仅被其参考计数捕获。 Perl 5在其GC实施中没有循环检测器,并且可能会泄漏循环的内存。
还进行了研究以充分利用这两个方面的优势(低暂停时间,高吞吐量):
http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf
回答
在游戏后期,但是我认为使用python进行RC的一个重要理由是它的简单性。例如,请参阅Alex Martelli的这封电子邮件。
(我找不到Google缓存之外的链接,该日期是python列表上的电子邮件日期,从2005年10月13日开始)。