垃圾收集器是否会导致内存泄漏?

12

如果我有一个垃圾收集器,跟踪每个被分配的对象,并在它们不再具有可用引用时释放它们,那么你仍然可能拥有内存泄漏吗?

考虑到内存泄漏是指没有任何引用的分配,那不是不可能的吗?或者我错过了什么吗?

编辑:因此,我将不再在代码中引用的分配计算为内存泄漏。大量累积的分配,您仍然有引用并不是我所考虑的泄漏。

我也只谈论普遍的现代垃圾收集器,虽然这已经过去一段时间,但我知道像循环引用之类的情况不会使它们失灵。 我不需要任何语言的具体答案,这只是我和朋友进行的对话。我们正在谈论Actionscript和Java,但我不关心特定于这些语言的答案。

编辑2:听起来,似乎没有理由代码可以完全失去引用分配的能力而不会被GC捕获,但我仍在等待更多意见。


“内存泄漏”的定义并不是十分明确。我建议您通过参考资料来进一步明确该定义。 - Michael Petrotta
尚未被踩,但由于您尚未提供“内存泄漏”的定义和您所考虑的GC类型,因此这并不令人意外。还要检查它是否与某种作业有关,并在需要时添加“作业”标签。 - Alexei Levenkov
完全不是作业,只是和朋友聊起了ActionScript和Java,我不知道内存泄漏是否对它们构成问题。 - Robot Rocker
我也更加具体地说明了那些其他的定义,谢谢。但是,在一开始时我被投了反对票,不过后来就没事了。 - Robot Rocker
我不认为“内存泄漏”特别模糊。实际上,当一个程序需要处理一系列重复出现的输入,而这些输入只会使其进入有限数量的可观察状态,但在某个点之后需要无限量的内存来处理时,该程序就存在内存泄漏。我想有些情况下这个术语可能是模糊的(例如,通过缓冲文件并输出结果来确定文件中行数是偶数还是奇数的程序)。可以说,这样的程序只有两个可观察状态。 - supercat
4个回答

18
如果您的问题确实是这样的:
考虑到内存泄漏是没有任何引用的分配,这不是不可能的,还是我漏了什么?
那么答案是“是的,那是不可能的”,因为一个正确实现的垃圾回收器会回收所有没有活动引用的分配。
然而,在Java中您肯定可以有一个“内存泄漏”。 我对“内存泄漏”的定义是具有活动引用(因此它不会被垃圾回收器回收)但程序员不知道该对象无法被回收(即:对于程序员来说,该对象已经死亡并应该被回收)。 一个简单的例子如下所示:
ObjectA - > ObjectB
在这个例子中,ObjectA是代码中正在使用的一个对象。 但是,ObjectA包含对ObjectB的引用,该引用实际上是无效的(即:ObjectB已经被分配和使用,并且现在从程序员的角度来看已经死亡),但程序员忘记了将ObjectA中的引用设置为null。 在这种情况下,ObjectB已经“泄漏”。
听起来不像是一个大问题,但是有一些情况下,这些泄漏是累积的。 让我们想象一下,ObjectA和ObjectB实际上是同一个类的实例。 并且程序员忘记将引用设置为null的问题发生在每次使用这样的实例时。 最终,您会得到像这样的东西:
ObjectA-> ObjectB-> ObjectC-> ObjectD-> ObjectE-> ObjectF-> ObjectG-> ObjectH-> etc...
现在,ObjectB到ObjectH都泄漏了。 这种问题(最终)会导致您的程序崩溃。 即使有一个正确实现的垃圾回收器也是如此。

这个答案看起来最好,但是例子对我来说有点不清楚:你是在说这段代码是否仍然引用了ObjectA吗? - Robot Rocker
@supercat,我认为内存泄漏不一定会导致程序崩溃,因此如果给予足够的时间和内存,那么没有根对象关心的活动对象的百分比不必接近100%。考虑到它渐进地接近50%的情况。现在程序使用的内存是其所需内存的两倍。如果可以避免这种情况,那么这就是一个泄漏。 - CaTalyst.X
@CaTalyst.X:有很多原因可能导致程序需要两倍(甚至10倍)于其“所需”的内存,但这并不一定被称为内存泄漏。例如,一个创建了许多List<Guid>但最终每个列表中只放入了平均两个项目的程序,只会使用分配给GUID的空间的1/8(即使如此),但这并不算是“泄漏”。我认为,在给定某个无限输入流的情况下,能够用有限的RAM满足其内存需求的程序与不能做到这一点的程序之间存在质的差异。 - supercat
@CaTalyst.X:可能会出现这样的问题,即实际程序所需的内存量随着输入大小的增加而无限增长,尽管所需状态的实际数量是有限的。例如,如果输入由任意精度数字后跟一个32位整数组成,并且应返回该数字对整数取模的结果,则16GB的RAM足以容纳状态(到目前为止读取的数字,对每个值1到2^32-1取模),无论输入有多大,但将所有内容读入RAM然后... - supercat
执行一次模运算可能比在每个输入字节之前更新每个可能的模数更实际,然后在输入流的末尾发现哪些值实际上是必需的。[当然,您不必维护所有2 ^ 32个模数值,但必须维护足够的值以使该方法变得不切实际]。 - supercat
显示剩余2条评论

3
为了确定一个程序是否存在内存泄漏,首先必须定义什么是内存泄漏。我将定义一个程序具有内存泄漏,如果存在某些状态S和一系列输入I,使得:
1. 如果程序处于状态S并接收到输入I,则它仍将处于状态S(如果不崩溃),但... 2. 重复上述序列N次所需的内存量将无限增加。
在完全在垃圾回收框架中运行的程序中,按照上述定义可能会发生内存泄漏。这种情况通常发生在事件订阅中。
假设线程安全集合公开一个CollectionModified事件,并且其IEnumerable.GetEnumerator()方法返回的IEnumerator在创建时订阅该事件,并在Dispose时取消订阅;该事件用于在集合被修改时使枚举能够正常进行(例如,确保始终在枚举期间连续返回集合中的对象;在其中存在一部分对象将最多返回一次)。现在假设创建了一个长寿实例的集合类,并且某些特定的输入将导致对其进行枚举。如果CollectionModified事件对每个未处理的IEnumerator都持有强引用,那么重复枚举集合将创建并订阅无限数量的枚举器对象。内存泄漏。

1

内存泄漏不仅取决于垃圾回收算法的效率,如果您的程序保留了具有长生命周期的对象引用,例如实例变量或静态变量而没有使用,那么您的程序将出现内存泄漏。

引用计数存在循环引用的已知问题。

Object 1 refers to Object 2 and Object 2 refers to Object 1 

但如果没有其他人引用对象1或对象2,则引用计数算法将在此情况下失败。

由于您正在处理垃圾收集器本身,因此值得阅读不同的实现策略


如果开发人员自己检查这两个对象是否都标记为null,他们想要删除它,那么它会起作用吗? - linuxeasy
感谢提供链接,我一定会阅读相关材料。大多数垃圾回收器应该可以处理循环引用。 - Robot Rocker
1
@RobotRocker 是的,现代的垃圾回收器可以处理循环引用计数。 - mprabhat

0

使用另一种垃圾回收器GC也可能会导致内存泄漏:如果您使用保守型垃圾回收器,该回收器会天真地扫描内存,并针对所有看起来像指针的内容,不释放其“指向”的内存,则可能会留下无法访问的已分配内存。


我正在阅读您提供的链接,但似乎它使用的垃圾收集器不太理想。 - Robot Rocker
如果您始终可以重构内存中可访问的确切对象的图形,则应使用精确垃圾收集器,而不是保守垃圾收集器(这确实“不太理想”)。但是,在实践中使用保守垃圾收集器,在没有此类信息的实现中使用,请参见此链接以获取一些详细信息:http://www.hpl.hp.com/personal/Hans_Boehm/gc/conservative.html - gfour

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接