为啥重新启动活动时堆内存会增加?

Posted

技术标签:

【中文标题】为啥重新启动活动时堆内存会增加?【英文标题】:Why does heap memory increase when re-launching an activity?为什么重新启动活动时堆内存会增加? 【发布时间】:2011-10-13 16:55:38 【问题描述】:

这个问题与 android 中的内存有关。

我的方法:

我有两个活动,A 和 B。我从 A 启动 B,如下所示:

Intent i = new Intent(A.this, B.class);
startActivity(i);

在 B 中单击按钮时,我这样做:

B.this.finish();
在 B 中,我重写了 onDestroy 方法并将所有引用设置为 null。 我没有在A的onResume方法中分配新内存。 我没有泄露上下文。 我没有使用多线程。 我没有使用服务。 B中的所有变量都是私有类变量,在B的onDestroy中全部设置为null。 另外,B 中的 ImageView 在 B 的 onDestroy 中将其背景设置为 null。 我确定 B 会被摧毁。

结果:

当我在活动 A 中时,堆内存为 7.44 MB。然后当我启动 B 并在 B 上调用完成(并因此返回到 A)时,堆增加了 0.16 MB。再次重复此过程,堆每次增加 0.08 MB。

我看的不是堆限制,而是分配的堆。 我在 B 的 onDestroy 方法结束时调用 System.gc()。

其他信息:

-我已使用 MAT 分析内存分配并尝试找到此泄漏。奇怪的是,活动 B 似乎有 5 个实例。碰巧的是,我重复了 startActivity/finish 过程 5 次。最下面的条目是Activity,其他的是Activity中的监听器:

这是支配树的截图。我找不到任何异常或可疑之处。

-我已经观看了有关内存使用(和泄漏)的两个谷歌 IO 视频。

问题:

无论我做什么,这 0.08 MB 的堆是否可能总是被分配(并且不能被 GC 收集)?如果不是,您知道是什么原因造成的吗?

更新:

    我尝试在 B 中未设置内容视图的情况下启动活动 B。这意味着 B 是一个完全空的活动。结果是当我多次重新启动活动时堆内存没有增加。但是请注意,这不是解决方案。我必须能够设置内容视图。

    scorpiodawg:我尝试在模拟器上运行我的应用程序,但堆仍在增长。不过还是不错的尝试。

    ntc:我将所有出现的“this”更改为可能的“getApplicationContext()”。我无法调用 setContentView(getApplicationContext());因为 setContentView 想要对布局文件的引用,而不是上下文。我所做的是创建一个空布局文件并调用 setContentView(emptylayout);在 Activity B 的 onDestroy 方法中。这没有帮助。

    我试图删除所有代码,以便只调用 setContentView(mylayout)。问题依然存在。然后我删除了布局 XML 文件中的所有 gui 元素。问题依然存在。唯一剩下的就是容器视图,几个嵌套的线性布局、相对布局和滚动布局。我试图删除在滚动条中设置“android:scrollbarDefaultDelayBeforeFade”属性。结果很好,内存泄漏消失了。然后我把之前删除但没有设置“android:scrollbarDefaultDelayBeforeFade”属性的所有代码放回去,内存泄漏又回来了。这有多奇怪?

【问题讨论】:

但是如果你一次又一次地重复这个过程,你会得到OutOfMemory异常吗? 是的,最终我会的。 你是如何定义和设置你的听众的?他们是什么样的听众? 这个问题与我的听众无关。我尝试删除所有侦听器,但问题仍然存在,堆内存仍然增加。 在 MAT 屏幕截图中,我可以看到只有 Listener。您是否在 onDestroy 方法中重置它们? mView.setOnClickListener(null); 【参考方案1】:

考虑使用

android:launchMode="singleTask"

在您的 AndroidManifest.xml 中。 See this.

【讨论】:

【参考方案2】:

您的活动中似乎存在内存泄漏。

您可能正在泄漏上下文(在本例中为活动)。因此,当您在活动中调用 onDestroy 方法时,请确保对上下文的所有引用都进行了清理。 More details here.

还可以查看可能在您完成活动时未被取消注册的内容观察者。

【讨论】:

引用我自己的话:“在 B 中,我重写了 onDestroy 方法并将所有引用设置为 null。”和“我没有泄露上下文。” 在 MAT 中似乎 Activiy 被泄露了。我猜您已经使用 MAT 遵循了 GC 根的路径。听众还对活动或活动的任何成员有任何参​​考吗?正如你所说,有很多关于 IO 视频等内存泄漏的文档,但对我来说,这个参考总是有帮助的:grubber.blog.hexun.com/58466974_d.htmlCheers。 我遵循了 GC 根路径,没有发现任何可疑之处。尽管我必须承认,我不太确定要寻找什么。至少我找不到任何属于我的变量。至于 MAT 截图,是什么让你认为有泄漏?至于侦听器,我尝试在 B 中完全不使用侦听器进行编译,但堆增加量保持不变。与我的听众无关。【参考方案3】:

我认为,这是一个典型的 java 问题。杀死一个 Activity 并不意味着它的关联对象应该从heap 中删除,即使它们在失落的土地上(它们的引用为空)。因为它在调用Garbage collector 时完全依赖于虚拟机(不管你说System.GC())。所以当这种情况几乎没有内存时,它会调用它并清理它(不能再次确定,可能是在他们的引用变为null之后立即),所以我认为你不应该担心它。

已编辑: 拨打setContentView(getApplicationContext);

无论您在哪里使用this 关键字来传递上下文,请将其更改为this.getApplicationContext()

【讨论】:

我应该担心它,因为当我多次重启 Activity B 时,我得到了一个 OOM 异常,以至于 0.08 MB 最终堆积起来超过了最大堆限制。当几乎没有剩余内存时,GC 不会突然介入并保存我的应用程序。【参考方案4】:

无论我做什么,这 0.08 MB 的堆是否可能总是被分配(而不是由 >GC 收集)?如果不是,您知道是什么原因造成的吗?

0.08MB 的堆如果没有被使用,会在系统认为需要时被回收。垃圾收集不会在您调用 System.gc() 后立即发生,它更像是要求 GC 尽快发生。在分配对象和从内存中删除对象之间通常有 LONG 时间。 Java 虚拟机规范和 Java 语言规范指定经历以下阶段的对象的生命周期:

    已创建 使用中(强可达) 不可见 无法访问 已收集 定稿 已解除分配

请注意,当一个对象无法访问时,它只会成为垃圾回收的候选对象,因为它可能会发生。 即使像 Dalvik 这样占用空间很小的 JVM,我也不认为一个对象会在这么短的时间内经历完整的生命周期。 GC 是一项昂贵的操作,因此仅在需要时才进行。

如果您有需要快速从内存中删除的对象(出于某种原因),请尝试 WeakReference 。或者,也许您可​​以提供更多关于您正在尝试做的事情的背景信息,并且有人可以通过这种方式更好地帮助您。

有关 GC 在 Java 中如何发生的更多信息, this is a pretty good article

【讨论】:

感谢您的链接,有用的东西。但是,它使我得出结论,即 0.08 MB 永远不会达到“无法访问”状态。这令人担忧,因为我在 Activity B 的 onDestroy 方法中将所有引用设置为 null。所以基本上,我不知道是什么分配了 0.08 MB 内存。 您将引用设置为 null 并不会删除该引用曾经持有的对象。这 0.08 MB 被很多东西占用。从创建活动的意图到活动的上下文,以及许多其他事情。是你只是在修补试图理解的东西,还是你心中有一些你无法弄清楚如何完成的目标?并不是说内存管理与您无关,但在大多数情况下,您永远不必担心太多。只需打开 logcat 看看 GC 发生的频率。 你关心的0.08MB,可能已经到了无法到达的状态。但是,在将对象从内存中删除之前,它们至少还要经历两个状态。顺便说一句,即使在最终确定阶段,对象也很有可能再次变得可访问。 您一次又一次地启动和重新启动 Activity B 并不是一个好主意。如果必须这样做,请尝试类似活动的单实例启动模式,并确保您表明您将自己处理方向和键盘更改。这应该(不完全确定)确保只创建一个活动实例。【参考方案5】:

如果您有 5 个活动 B 实例,那么您没有正确管理活动堆栈。 我发现检查它的最佳方法是使用 CLI 命令:

adb shell dumpsys meminfo '您的应用程序包名称'

当我在两个活动项目之间切换时,我遇到了类似的问题。每次切换时,我都会在堆栈上获得一个新实例,如上述命令所示。然后,我将已启动活动的标志设置为 FLAG_ACTIVITY_REORDER_TO_FRONT,代码如下:

Intent i = new Intent("com.you.yourActivityB");
i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(i);

完成此操作后,当我在两个活动之间切换时,adb shell 命令不会显示更多实例

【讨论】:

设置 i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);在这两个活动上都成功了!在这个问题上花了大约 30 多个小时后,我感激不尽!

以上是关于为啥重新启动活动时堆内存会增加?的主要内容,如果未能解决你的问题,请参考以下文章

应用程序从错误的活动重新启动

收集应用程序内存后重新启动时启动画面未重新启动

重新启动活动时,Android Wear 上的融合位置提供程序会导致崩溃

如何在特定时间重新启动应用程序

iOS MagicalRecord 之谜。为啥在 truncateAll 后重新启动时我的数据会重新出现?

为啥 SparkContext 会随机关闭,如何从 Zeppelin 重新启动?