你好!
我是Java和Android的初学者,最近在处理应用程序的内存管理时遇到了麻烦。我将这些文本分成几部分,以使其更清晰易读。
这是一个由几个阶段(关卡)组成的游戏。每个阶段都有玩家的起点和出口,出口将玩家带入下一个阶段。每个阶段都有自己的障碍。当前,当玩家到达最后阶段(到目前为止,我只创建了4个)时,他/她会自动返回第一阶段(第1级)。
名为 GameObject 的抽象类(扩展了 Android.View )定义了玩家以及游戏中存在的所有其他对象(障碍物)的基本结构和行为。所有对象(本质上是视图)都在由我创建的自定义视图中绘制(扩展了FrameLayout)。游戏逻辑和游戏循环由侧线程(gameThread)处理。这些阶段是通过从xml文件检索元数据创建的。
除了我的代码上所有可能的内存泄漏(我一直在努力寻找和解决所有这些问题)外,还有一个与垃圾收集器发生有关的奇怪现象。我将使用图像而不是用文字描述它并冒着使您困惑的风险。正如孔子所说:“一个图像值得一千个单词”。好吧,在这种情况下,由于我的下面的GIF包含150帧,因此我已经避免了阅读15万个单词的麻烦。
说明:第一张图片代表首次加载“ stage 1”时我的应用程序的内存使用情况。第二张图片(GIF)首先代表我的应用程序的内存使用时间线,这是第二次加载“阶段1”时(如前所述,当玩家击败最后一个阶段时,会发生这种情况),然后是四个垃圾我强行发起的收藏。
您可能已经注意到,两种情况之间的内存使用量存在巨大差异(将近50MB)。第一次加载“阶段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)
...还有更多我找不到的东西。
答案 0 :(得分:4)
垃圾收集很复杂,不同的平台实施方式也不同。实际上,同一平台的不同 version 实施垃圾收集的方式有所不同。 (还有更多……)
典型的现代收藏家所基于的观察是,大多数对象都死于年轻;也就是说,它们在创建后不久就无法访问。然后将堆划分为两个或更多的“空间”。例如一个“年轻”空间和一个“旧”空间。
(还有其他各种聪明又复杂的东西……我不会介绍。)
您的问题是,为什么直到您致电System.gc()
之前,空间使用量才不会显着下降。
答案基本上是,这是一种有效的处理方式。
收集的真正目的不是一直释放尽可能多的内存。相反,目标是确保在需要时有足够的可用内存,并以最小的CPU开销或最少的GC暂停来做到这一点。
因此,在正常操作中,GC的行为将与上述相同:频繁进行“新”空间收集,而不频繁进行“旧”空间收集。和收藏 将“根据需要”运行。
但是,当您调用System.gc()
时,JVM通常会 尝试获取尽可能多的内存。这意味着它会执行“完整gc”操作。
现在,我认为您说过需要进行两次System.gc()
调用才能产生真正的变化,这可能与使用finalize
方法或Reference
对象或类似对象有关。事实证明,主GC已由后台线程完成之后,处理了可终结对象和Reference
。对象实际上仅处于可以在之后收集和删除它们的状态。因此,需要另一个GC才能最终摆脱它们。
最后,总堆大小是一个问题。当堆太小时,大多数VM会从主机操作系统请求内存,但是不愿意将其交还给内存。原因是,如果垃圾与非垃圾的比例很高,则收集器通常效率最高。通过将清空的内存还给操作系统,JVM将会在以后影响性能……如果堆再次增长。
我不确定Android收集器,但是Oracle收集器会在连续的“完整”收集结束时注意到可用空间比率。只有在给定的循环次数后可用空间比率“过高”时,它们才会减小堆的总体大小。
假设Android收集器的工作方式相同,这是为什么您必须多次运行System.gc()
来缩小堆大小的另一种解释。
答案 1 :(得分:-2)
我的应用程序遇到了同样的问题,我看到您已经了解了GC,请尝试观看video上为什么需要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调用一次