垃圾收集器没有像在Android应用程序中那样释放“捶打内存”

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了垃圾收集器没有像在Android应用程序中那样释放“捶打内存”相关的知识,希望对你有一定的参考价值。

你好!

我是初学者Java和android开发人员,我最近一直在处理应用程序的内存管理问题。我将把这个文本分成几个部分,以使其更清晰和可读。


我的应用程序的简要说明

这是一个由几个阶段(级别)组成的游戏。每个阶段都有一个玩家的起点和一个出口,引导玩家进入下一个阶段。每个阶段都有自己的障碍。目前,当玩家到达最后阶段(我到目前为止只创造了4个)时,他/她会自动回到第一阶段(等级1)。 一个名为GameObject的抽象类(扩展Android.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)。当首次加载“Stage 1”时,当游戏开始时,应用程序使用85MB的内存。当第二次加载同一个阶段时,稍后,内存使用量已经达到130MB!这可能是由于我的一些不良编码而我因此而不在这里。你有没有注意到,在我强行执行2(实际上只有4个,但只有前2个重要的)垃圾收集后,内存使用率又回到了“正常状态”(与首次加载阶段时的内存使用量相同)?这就是我所说的奇怪现象。

这个问题

如果垃圾收集器应该从不久引用的内存对象中删除(或者至少只有弱引用),为什么只有在我强行调用GC时才会删除上面看到的“垃圾内存”不是GC的正常执行?我的意思是,如果我手动启动的垃圾收集可以删除这个“捶打”,那么普通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) ......还有更多我再也找不到了。

答案

垃圾收集很复杂,不同的平台以不同的方式实现垃圾收集。实际上,同一平台的不同版本以不同方式实现垃圾收集。 (和更多 ... )

一个典型的现代收藏家是基于大多数物体年轻的观察;即它们在创建后很快就会无法访问。然后将堆分成两个或更多个“空格”;例如一个“年轻”的空间和一个“旧的”空间。

  • “年轻”空间是创建新对象的地方,它经常被收集。 “年轻”的空间往往更小,“年轻”的收藏很快就会发生。
  • “旧”空间是长寿命物体的最终结果,并且不经常收集。在“旧”空间收集往往更昂贵。 (由于各种原因。)
  • 在“新”空间中经历了多个GC循环的对象得到“终身”;即他们被转移到“旧”空间。
  • 偶尔我们可能会发现我们需要同时收集新旧空间。这称为完整集合。完整的GC是最昂贵的,并且通常在相当长的时间内“停止世界”。

(还有各种其他聪明而复杂的东西......我不会介绍。)


您的问题是为什么在您调用System.gc()之前空间使用量不会显着下降。

答案基本上是这是做事的有效方式。

收集的真正目标不是一直释放尽可能多的内存。相反,目标是确保在需要时有足够的可用内存,并以最小的CPU开销或最少的GC暂停来执行此操作。

因此,在正常操作中,GC将表现如上:经常“新”空间集合和不太频繁的“旧”空间集合。收集将“按需”运行。

但是当你调用System.gc()时,JVM通常会尝试尽可能多地恢复内存。这意味着它做了一个“完整的gc”。

现在我想你说需要几个System.gc()调用才能产生真正的差异,这可能与使用finalize方法或Reference对象或类似物有关。事实证明,在主要GC由后台线程完成后处理可终结对象和Reference。实际上,对象实际上处于可以在此之后收集和删除的状态。因此需要另一个GC来最终摆脱它们。

最后,存在整个堆大小的问题。当堆太小但是不愿意将其还原时,大多数VM从主机操作系统请求内存。原因是如果垃圾与非垃圾的比例很高,收集者通常效率最高。通过将空的内存提供给操作系统,JVM将会影响性能......如果堆再次增长。

我不确定Android收集器,但Oracle收集器注意到连续“完整”收集结束时的可用空间比率。如果在给定的循环次数之后自由空间比率“太高”,它们只会减小堆的整体大小。

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

以上是关于垃圾收集器没有像在Android应用程序中那样释放“捶打内存”的主要内容,如果未能解决你的问题,请参考以下文章

Android 应用程序不像在 Android Studio 中那样显示在设备上

自动释放池与垃圾收集有啥联系?

Android 内存管理介绍

Android内存优化篇

Lua垃圾收集

java有自动垃圾回收机制