为什么这是内存泄漏。

10

我发现了一个用于在 Android(Java)中检测内存泄漏的库,叫做LeakCanary,但是我无法理解他们泄露内存的示例。有人能否解释一下为什么他们示例中的代码是内存泄漏,以及为什么会出现这种情况?

class Cat {
}
class Box {
  Cat hiddenCat;
}
class Docker {
  static Box container;
}

// ...

Box box = new Box();
Cat schrodingerCat = new Cat();
box.hiddenCat = schrodingerCat;
Docker.container = box;

然后他们观察变量schrodingerCat是否泄漏,这导致一个泄漏如下所示(我不知道如何将其与上述代码相关联)。

* GC ROOT static Docker.container
* references Box.hiddenCat
* leaks Cat instance
任何关于漏洞解释以及检测与之相关的帮助都将非常有用。对于初学者来说,一些好的文章也会很不错。
谢谢!
3个回答

14

首先,让我们了解什么是内存泄漏

定义

内存泄漏是指在RAM中分配的数据(位图,对象,数组等),虽然程序不再需要它们,但垃圾收集器(GC)无法释放。

示例

用户正在打开一个显示图像的视图。我们将位图加载到内存中。现在用户退出视图,不再需要该图像并且代码中也没有对其的引用。此时,GC会开始工作并从内存中删除它。但是,如果我们仍然有对它的引用,GC将不知道可以删除它并且它将一直留在内存中占用不必要的空间 - 即内存泄漏

盒中之猫

假设我们的应用程序中有一个Cat对象,并且我们将它保存在Box对象中。如果我们保存了盒子(持有Box对象的引用)并且盒子保存了猫,则GC将无法从内存中清除猫对象。

Docker是一个具有对Box的静态引用的类。这意味着除非我们将其置空或重新分配值,否则Docker将继续引用Box。这会防止GC清除盒子(和内部的猫)。

那么,我们需要这只猫吗?它对应用程序仍然有用吗?

这取决于开发人员决定需要多长时间来使用这只猫。LeakCanary和其他诊断工具可能会提示可能存在内存泄漏。他们认为对象(Cat)可能不再需要,因此警告它是一个泄漏。

总结

在示例中,他们提供了一个常见的内存泄漏场景。当使用静态引用时,我们会阻止GC清除对象。你应该阅读这篇文章:

* GC ROOT static Docker.container
* references Box.hiddenCat
* leaks Cat instance

如下:

  • 对象Cat可能不再被使用,但并未被垃圾回收器从内存中删除。
  • Cat对象没有被删除的原因是因为Box具有对它的引用。
  • Box对象没有被删除的原因是因为Docker具有对它的静态引用。
  • Docker的静态引用是导致可能泄漏的树的根源。

2
这个很棒的解释应该被添加到LeakCanary的维基上 :) - Andrii Kovalchuk

1
似乎这里使用了RefWatcher实例来"监视变量schrodingerCat的泄漏":
refWatcher.watch(schrodingerCat);

强制进行一系列GC操作,如果传入的引用在这些GC操作期间没有被回收,就会被视为泄漏。 由于静态变量Docker.container.hiddenCat保持着对最初称为schrodingerCat的对象的GC根引用,因此它无法被GC回收,当您要求RefWatcher检查它时,它会让您知道该对象无法被回收。

1
哎?你能不能再简单一点解释给我听? :) - Bootstrapper
Docker.container.hiddenCat 是对创建为 schrodingerCat 的对象的静态引用。因此,在 RefWatcher 激活该对象时,该对象无法被垃圾回收。 - Michael Burr

1

我建议你阅读这个答案 https://dev59.com/GWct5IYBdhLWcg3wqvTL#11908685

它可能会帮助你理解上面的例子。

简而言之,在你的例子中,类Docker保留了对Box的引用。即使容器Box不再需要,类Docker仍然持有对它的引用,从而导致内存泄漏。

如果有帮助,请告诉我。


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