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

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

Java 为什么选择这种策略以及这样做的好处是什么?

这比 Python 方法更好吗?

有帮助吗?

解决方案

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

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

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

其他提示

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

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

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

此外,引用计数 GC 需要一个循环检测器来清理循环中仅由引用计数无法捕获的任何对象。Perl 5 在其 GC 实现中没有循环检测器,因此可能会泄漏循环内存。

我们还进行了研究以实现两全其美(短暂停时间、高吞吐量):http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

达伦·托马斯给出了一个很好的答案。然而,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 风格的分代垃圾收集器,它可以在单独的线程中运行,并且具有所有这些疯狂的优化等。编写代码的方式的差异应该可以忽略不计,最好是不存在。

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

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

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

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

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

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

我认为,与 iOS(基于 ObjectiveC,使用 RC)的流畅度相比,这实际上是 Android(基于 Java)即使在最昂贵的手机上也会出现卡顿感的主要原因。

我希望看到一个 jvm 选项来启用 RC 内存管理,并且也许保留 GC 仅在没有剩余内存时作为最后的手段运行。

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

例如,对于所有不喜欢默认 Sun Java VM GC 行为的“停止世界”方法的人,有以下 VM IBM 的 WebSphere 实时 它允许实时应用程序在 Java 上运行。

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

在多线程环境中,引用计数尤其难以有效地进行。我不知道如果不进入硬件辅助事务或类似的(当前)不寻常的原子指令,您将如何开始执行此操作。

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

游戏后期,但我认为 Python 中 RC 的一个重要理由是它的简单性。看到这个 亚历克斯·马尔泰利的电子邮件, , 例如。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top