java - 垃圾收集器没有像在 Android 应用程序中那样释放 "trash memory"

标签 java android performance memory-leaks garbage-collection

你好!

我是一名初级 Java 和 Android 开发人员,最近在处理我的应用程序的内存管理方面遇到了麻烦。我将把这篇文章分成几个部分,以使其更清晰易读。

我的应用程序的简要说明

这是一个由几个阶段(级别)组成的游戏。每个阶段都有一个玩家的起点和一个导出,引导玩家进入下一个阶段。每个阶段都有自己的一套障碍。目前,当玩家到达最后阶段(我目前只创建了 4 个)时,他/她会自动回到第一阶段(1 级)。

一个名为 GameObject(扩展 Android.View)的抽象类定义了玩家和游戏中存在的所有其他对象(障碍物等)的基本结构和行为。所有对象(本质上是 View )都绘制在我创建的自定义 View 中(扩展 FrameLayout)。游戏逻辑和游戏循环由一个侧线程(gameThread)处理。这些阶段是通过从 xml 文件中检索元数据来创建的。

问题

除了我的代码中所有可能的内存泄漏(我一直在努力寻找和解决所有这些问题)之外,还有一个与垃圾收集器发生相关的奇怪现象。我将使用图像,而不是用文字来描述它并冒着让您感到困惑的风险。孔子说:“千言万语”。好吧,在这种情况下,我刚刚让您免于阅读 150,000 个单词,因为我下面的 GIF 有 150 帧。

Stage 1 when firstly loaded. The total memory allocation of the app is 85mb.
Stage 1 when loaded for the second time. The total memory allocation of the app is 130mb. After I forcefully perform 2 garbage collections (using Android Profiler), the memory goes back to 85mb.

说明:第一张图片代表我的应用程序在第一次加载“阶段 1”时的内存使用情况。第二个图像 (GIF) 首先表示第二次加载“阶段 1”时我的应用程序的内存使用时间线(这种情况发生,如前所述,当玩家击败最后一个阶段时),然后是四个强制启动的垃圾收集由我。

您可能已经注意到,这两种情况在内存使用方面存在巨大差异(几乎 50MB)。当“第一阶段”首次加载时,当游戏开始时,应用程序使用 85MB 内存。第二次加载同一个stage的时候,稍晚一点,内存使用量已经是130MB了!这可能是由于我的一些糟糕的编码,因此我不在这里。你有没有注意到,在我强行执行了 2 次(实际上是 4 次,但只有前 2 次重要)垃圾收集之后,内存使用情况又回到了它的“正常状态”(与第一次加载舞台时的内存使用情况相同)? 这就是我所说的奇怪现象 .

问题

如果垃圾收集器应该从不再被引用的内存对象中删除(或者至少只有弱引用),为什么你在上面看到的“垃圾内存”只有在我强行调用 GC 和不是关于GC的正常执行?我的意思是,如果我手动启动的垃圾收集可以删除这个“thrash”,那么正常的 GC 执行也可以删除它。为什么没有发生?

我什至尝试在切换阶段时调用 System.gc(),但是,即使发生了垃圾收集,也不会像我手动执行 GC 时那样删除这个“颠簸”内存。我是否遗漏了一些关于垃圾收集器如何工作或 Android 如何实现它的重要信息?

最后的考虑

我花了几天时间搜索、研究和修改我的代码,但我找不到为什么会这样。 StackOverflow 是我最后的选择。谢谢!

注意:我打算发布一些可能与我的应用程序源代码相关的部分,但由于问题已经太长了,我将在这里停止。如果您觉得需要检查某些代码,请告诉我,我将编辑此问题。

我已经读过的:

How to force garbage collection in Java?

Garbage collector in Android

Java Garbage Collection Basics by Oracle

Android Memory Overview

Memory Leak Patterns in Android

Avoiding Memory Leaks in Android

Manage your app's memory

What you need to know about Android app memory leaks

View the Java heap and memory allocations with Memory Profiler

LeakCanary (memory leak detection library for Android and Java)

Android Memory Leak and Garbage Collection

Generic Android Garbage Collection

How to clear dynamically created view from memory?

How References Work in Android and Java

Java Garbage Collector - Not running normally at regular intervals

Garbage Collection in android (Done manually)

......还有更多我再也找不到了。

最佳答案

垃圾回收比较复杂,不同平台实现方式不同。事实上,同一平台的不同版本实现垃圾收集的方式不同。 (和更多 ... )
一个典型的现代 Collection 家是基于观察到的大多数元素都很年轻;即它们在创建后很快就变得无法访问。然后将堆分成两个或多个“空间”;例如一个“年轻”的空间和一个“老”的空间。

  • “年轻”空间是创建新对象的地方,它经常被收集。 “年轻”的空间往往更小,“年轻”的 Collection 发生得很快。
  • “旧”空间是长期存在的对象结束的地方,它很少被收集。在“旧”空间 Collection 往往更贵。 (出于各种原因。)
  • 在"new"空间中存活多次 GC 周期的对象将获得“终身使用权”;即它们被移动到“旧”空间。
  • 偶尔我们可能会发现需要同时收集新旧空间。这称为完整集合。完整的 GC 是最昂贵的,并且通常会在相对较长的时间内“停止世界”。

  • (还有各种其他巧妙而复杂的东西……我不会深入讨论。)

    您的问题是为什么在您拨打 System.gc() 之前,空间使用量不会显着下降。 .
    答案基本上是,这是做事的有效方式。
    收集的真正目标不是一直释放尽可能多的内存。相反,目标是确保在需要时有足够的空闲内存,并以最小的 CPU 开销或最小的 GC 暂停来做到这一点。
    因此,在正常操作中,GC 的行为将如上:频繁地进行"new"空间收集和不频繁地进行“旧”空间收集。和 Collection 品
    将“根据需要”运行。
    但是当你拨打 System.gc() JVM 通常会尝试取回尽可能多的内存。这意味着它会执行“完整的 gc”。
    现在我想你说需要几个 System.gc()呼吁做出真正的改变,这可能与 finalize 的使用有关方法或 Reference物体或类似物。结果是 finalizable 对象和 Reference在主 GC 完成后由后台线程处理。对象实际上只是处于可以收集和删除之后的状态。所以需要另一个 GC 来最终摆脱它们。
    最后,还有整体堆大小的问题。当堆太小时,大多数 VM 会从主机操作系统请求内存,但不愿意将其归还。 Oracle 收集器会记录连续“完整”收集结束时的可用空间比率。如果在多次 GC 周期后可用空间比率“太高”,它们只会减小堆的整体大小。 Oracle GC 采用这种方法的原因有很多:
  • 当垃圾对象与非垃圾对象的比率很高时,典型的现代 GC 工作效率最高。因此,保持堆大有助于提高效率。
  • 应用程序的内存需求很有可能再次增长。但是 GC 需要运行才能检测到。
  • JVM 反复将内存返还给操作系统并重新请求它,这可能会破坏操作系统虚拟内存算法。
  • 如果操作系统缺少内存资源,就会出现问题;例如JVM:“我不需要这个内存。把它拿回来”,操作系统:“谢谢”,JVM:“哦……我又需要它了!”,操作系统:“不需要”,JVM:“OOME”。

  • 假设 Android 收集器的工作方式相同,这就是为什么您必须运行 System.gc() 的另一种解释。多次使堆大小缩小。

    在开始添加 System.gc() 之前调用您的代码,阅读 Why is it bad practice to call System.gc()? .

    关于java - 垃圾收集器没有像在 Android 应用程序中那样释放 "trash memory",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52177254/

    相关文章:

    java - 在 varchar 列中存储快速编码数据

    java - 使用模式和匹配器查找数字

    java - 如何创建类似 GMail.app 的菜单

    安卓自动更新

    android - 无法让 ScrollView 工作

    visual-studio - 证明更换硬件将提高开发人员的性能

    java - 如何在 java 中解码 ruby​​ 对象?

    performance - 如何衡量循环方法和递归方法之间代码速度的差异?

    python - Python 3 中循环、列表理解和映射的性能

    java - Android 1.5 和 dom4j - 属性名称解析不正确