![](/img/trans.png)
[英]Android Garbage Collector freeing memory in the middle of an AJAX request
[英]Garbage Collector not freeing "trash memory" as it should in an Android application
你好!
我是一名初级 Java 和 Android 开发人员,最近在处理我的应用程序的内存管理方面遇到了麻烦。 我将把这篇文章分成几个部分,以使其更清晰易读。
这是一个由几个阶段(级别)组成的游戏。 每个阶段都有一个玩家的起点和一个出口,引导玩家进入下一个阶段。 每个阶段都有自己的一套障碍。 目前,当玩家到达最后阶段(我目前只创建了 4 个)时,他/她会自动回到第一阶段(1 级)。
一个名为GameObject (扩展Android.View )的抽象类定义了玩家和游戏中存在的所有其他对象(障碍物等)的基本结构和行为。 所有对象(本质上是视图)都绘制在我创建的自定义视图中(扩展 FrameLayout)。 游戏逻辑和游戏循环由一个侧线程(gameThread)处理。 这些阶段是通过从 xml 文件中检索元数据来创建的。
除了我的代码中所有可能的内存泄漏(我一直在努力寻找和解决所有这些问题)之外,还有一个与垃圾收集器发生相关的奇怪现象。 我将使用图像,而不是用文字来描述它并冒着让您感到困惑的风险。 孔子说:“千言万语”。 好吧,在这种情况下,我刚刚让您免于阅读 150,000 个单词,因为我下面的 GIF 有 150 帧。
描述:第一张图片代表我的应用程序在第一次加载“stage 1”时的内存使用情况。 第二张图片 (GIF) 首先表示第二次加载“第 1 阶段”时我的应用程序的内存使用时间线(发生这种情况,如前所述,当玩家击败最后一个阶段时),然后是四个强制启动的垃圾收集由我。
您可能已经注意到,这两种情况在内存使用方面存在巨大差异(几乎 50MB)。 当“第一阶段”首次加载时,当游戏开始时,应用程序使用 85MB 的内存。 第二次加载同一个stage的时候,稍晚一点,内存使用量已经是130MB了! 这可能是由于我的一些糟糕的编码,因此我不在这里。 你有没有注意到,在我强行执行了 2 次(实际上是 4 次,但只有前 2 次重要)垃圾收集之后,内存使用情况又回到了它的“正常状态”(与第一次加载舞台时的内存使用情况相同)? 这就是我所说的奇怪现象。
如果垃圾收集器应该从不再被引用的内存对象中删除(或者至少只有弱引用),为什么你在上面看到的“垃圾内存”只有在我强行调用GC和不是关于GC的正常执行? 我的意思是,如果我手动启动的垃圾收集可以删除这个“thrash”,那么正常的GC执行也可以删除它。 为什么没有发生?
我什至尝试在切换阶段时调用System.gc() ,但是,即使发生了垃圾收集,也不会像我手动执行GC那样删除这个“thrash”内存。 我是否遗漏了一些关于垃圾收集器如何工作或 Android 如何实现它的重要信息?
我花了几天时间搜索、研究和修改我的代码,但我找不到为什么会这样。 StackOverflow 是我最后的选择。 谢谢!
注意:我打算发布一些可能与我的应用程序源代码相关的部分,但由于问题已经太长了,我将在这里停止。 如果您觉得需要检查某些代码,请告诉我,我将编辑此问题。
我已经读过的:
如何在 Java 中强制垃圾收集?
Android中的垃圾收集器
Oracle 的 Java 垃圾收集基础
Android 内存概览
Android 中的内存泄漏模式
避免 Android 中的内存泄漏
管理您的应用程序的内存
您需要了解的有关 Android 应用程序内存泄漏的信息
使用 Memory Profiler 查看 Java 堆和内存分配
LeakCanary(Android 和 Java 内存泄漏检测库)
Android 内存泄漏和垃圾收集
通用 Android 垃圾收集
如何从内存中清除动态创建的视图?
引用如何在 Android 和 Java 中工作
Java 垃圾收集器 - 无法定期正常运行
android中的垃圾收集(手动完成)
......还有更多我再也找不到了。
垃圾回收比较复杂,不同平台实现方式不同。 事实上,同一平台的不同版本实现垃圾收集的方式不同。 (和更多 ... )
一个典型的现代收藏家是基于对大多数物品年轻时死去的观察; 即它们在创建后很快就无法访问。 然后将堆分成两个或多个“空间”; 例如“年轻”空间和“旧”空间。
(还有各种其他巧妙而复杂的东西……我不会深入讨论。)
您的问题是为什么在您调用System.gc()
之前空间使用量不会显着下降。
答案基本上是,这是做事的有效方式。
收集的真正目标不是一直释放尽可能多的内存。 相反,目标是确保在需要时有足够的空闲内存,并以最小的 CPU 开销或最小的 GC 暂停来做到这一点。
因此,在正常操作中,GC 的行为将如上:频繁地进行“新”空间收集和不频繁地进行“旧”空间收集。 并且集合将“根据需要”运行。
但是当您调用System.gc()
,JVM通常会尝试取回尽可能多的内存。 这意味着它会执行“完整的 gc”。
现在我想你说过需要几次System.gc()
调用才能产生真正的不同,这可能与使用finalize
方法或Reference
对象或类似方法有关。 事实证明,在主 GC 完成后,后台线程会处理可终结对象和Reference
。 对象实际上只是处于可以收集和删除之后的状态。 所以需要另一个 GC 来最终摆脱它们。
最后,还有整体堆大小的问题。 当堆太小时,大多数 VM 会从主机操作系统请求内存,但不愿意将其归还。 Oracle 收集器会记录连续“完整”收集结束时的可用空间比率。 如果在多次 GC 周期后可用空间比率“太高”,它们只会减小堆的整体大小。 Oracle GC 采用这种方法的原因有很多:
当垃圾对象与非垃圾对象的比率很高时,典型的现代 GC 工作效率最高。 因此,保持堆大有助于提高效率。
应用程序的内存需求很有可能再次增长。 但是 GC 需要运行才能检测到。
JVM 反复将内存返还给操作系统并重新请求它,这可能会破坏操作系统虚拟内存算法。
如果操作系统缺少内存资源,就会出现问题; 例如 JVM:“我不需要这个内存。把它拿回来”,操作系统:“谢谢”,JVM:“哦……我又需要它了!”,操作系统:“不”,JVM:“OOME”。
假设 Android 收集器的工作方式相同,这就是为什么必须多次运行System.gc()
才能缩小堆大小的另一种解释。
在开始向代码添加System.gc()
调用之前,请阅读为什么调用 System.gc() 是不好的做法? .
我在我的应用程序上遇到了同样的问题,我看到您已经了解 GC,请尝试观看此视频,了解为什么需要 GC。 尝试将此代码添加到您的应用程序类(应用程序的java文件,就像每个活动的每个java文件一样)并将此代码添加到“onCreate”的覆盖下(代码在kotlin中)
这是洞类:
open class _appName_() : Application(){
private var appKilled = false
override fun onCreate() {
super.onCreate()
thread {
while (!appKilled){
Thread.sleep(6000)
System.runFinalization()
Runtime.getRuntime().gc()
System.gc()
}
}
}
override fun onTerminate() {
super.onTerminate()
appKilled = true
}
}
这段代码使得每 6 秒 GC 被调用一次
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.