本文介绍了垃圾收集器无法释放"thrash内存"就像在Android应用程序中一样的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

你好!

我是Java和Android的初学者,最近在处理应用程序的内存管理时遇到了麻烦.我将这些文本分成几部分,以使其更清晰易读.

I'm a beginner Java and Android developer and I've been having trouble lately dealing with my app's memory management. I will break this text into sections, in order to make it clearer and readable.

这是一个由几个阶段(关卡)组成的游戏.每个阶段都有玩家的起点和出口,出口将玩家带到下一个阶段.每个阶段都有其自己的障碍.当前,当玩家到达最后阶段(到目前为止,我只创建了4个)时,他/她会自动返回到第一阶段(第1级).

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

It's a game that consists of several stages (levels). Each stage has a starting point for the player and an exit, which leads the player to the next stage. Each stage has its own set of obstacles. Currently, when the player reaches the final stage (I've only created 4 so far) he/she automatically goes back to the first stage (level 1).

An abstract class called GameObject (extends Android.View) defines the base structure and behaviour for the player and all the other objects (obstacles, etc) present in the game. All the objects (that are, essentially, views) are drawn in a custom view created by me (extends FrameLayout). The game logic and the game loop is handled by a side thread (gameThread). The stages are created by retrieving metadata from xml files.

除了我的代码上所有可能的内存泄漏(我一直在努力寻找和解决所有问题)外,还有一个与垃圾收集器发生有关的奇怪现象.我将使用图像而不是用文字描述它并冒着使您困惑的风险.正如孔子所说:一个图像值得一千个单词".好吧,在这种情况下,由于我下面的GIF包含150帧,因此我已经避免了阅读15万个单词的麻烦.

Besides all the possible memory leaks on my code (all of which I've been working hard to find and solve), there is a strange phenomenon related to the garbage collector happening. Instead of describing it with words and risk getting you confused, I will use images. As Confucius said, "An image is worth a thousand words". Well, in this case, I've just saved you from reading 150,000 words, since my GIF below has 150 frames.

说明:第一张图片代表首次加载"stage 1"时我的应用程序的内存使用情况.第二张图片(GIF)首先代表我的应用程序的内存使用时间线,这是第二次加载阶段1"时(如前所述,当玩家击败最后一个阶段时,会发生这种情况),其后是四个垃圾我强行发起的收藏.

您可能已经注意到,两种情况之间的内存使用量存在巨大差异(将近50MB).第一次加载阶段1"时,在游戏开始时,该应用程序正在使用85MB的内存.当第二次加载同一阶段时,稍后,内存使用量已经达到130MB!那可能是由于我的编码不好,因此我不在这里.在我强制执行2个(实际上是4个,但只有前2个很重要)垃圾回收之后,您是否注意到内存使用率又回到了正常状态"(与首次加载该阶段时相同的内存使用率)? 那是我正在谈论的怪异现象.

Description: the first image represents my app's memory usage when the "stage 1" is first loaded. The second image (GIF) firstly represents my app's memory usage timeline when the "stage 1" is loaded for the second time (this happens, as described earlier, when the player beat the last stage) and is followed by four garbage collections forcefully initiated by me.

As you might have noticed, there is a huge difference (almost 50MB) in the memory usage between the two situations. When the "Stage 1" is firstly loaded, when the game starts, the app is using 85MB of memory. When the same stage is loaded for the second time, a little bit later, the memory usage is already at 130MB! That's probably due to some bad coding on my part and I'm not here because of this. Have you noticed how, after I forcefully performed 2 (actually 4, but only the first 2 mattered) garbage collections, the memory usage went back to it's "normal state" (the same memory usage as when the stage was firstly loaded)? That's the weird phenomenon I was talking about.

如果应该将垃圾收集器从内存中删除不再被引用的对象(或者至少仅具有弱引用),则为什么是仅当我强行调用 GC 而不是在 GC的正常执行时,您上面看到的垃圾内存"才被删除?我的意思是,如果由我手动启动的垃圾收集可以删除此崩溃",然后正常的 GC的执行也可以删除它.为什么不发生呢?

我什至尝试在切换阶段时调用 System.gc(),但是,即使发生了垃圾回收,这种崩溃"的内存也不会就像我手动执行 GC 一样删除了.我是否缺少有关垃圾收集器的工作方式或有关Android实现方式的重要信息?

If the garbage collector is supposed to remove from memory objects that are no long being referenced (or, at least, have only weak references), why is the "trash memory" that you saw above being removed only when I forcefully call the GC and not on the GC's normal executions? I mean, if the garbage collection manually initiated by me could remove this "thrash", then the normal GC's executions would be able to remove it as well. Why isn't it happening?

I've even tried to call System.gc() when the stages are being switched, but, even though the garbage collection happens, this "thrash" memory isn't removed like when I manually perform the GC. Am I missing something important about how the garbage collector works or about how Android implements it?

我花了几天的时间搜索,研究和修改我的代码,但我无法找出为什么会发生这种情况. StackOverflow是我的不得已的方法.谢谢!

I've spent days searching, studying and making modifications on my code but I could not find out why this is happening. StackOverflow is my last resort. Thank you!

注意::我打算在应用程序源代码中发布一些可能相关的部分,但是由于问题已经太久了,因此我将在此处停止.如果您需要检查一些代码,请告诉我,我将编辑此问题.

NOTE: I was going to post some possibly relevant part of my app's source code, but since the question is already too long I will stop here. If you feel the need to check some of the code, just let me know and I will edit this question.

我已经阅读的内容:
如何在Java中强制垃圾收集?
Android中的垃圾收集器
Oracle的Java垃圾收集基础
Android内存概述
Android中的内存泄漏模式
避免Android中的内存泄漏
管理应用的内存
您需要了解的有关Android应用内存泄漏的信息
使用Memory Profiler查看Java堆和内存分配
LeakCanary(适用于Android和Java的内存泄漏检测库)
Android内存泄漏和垃圾收集
通用Android垃圾收集
如何从内存中清除动态创建的视图?
引用如何在Android和Java
Java垃圾收集器-无法定期正常运行
Android中的垃圾收集(手动完成)
...还有更多我找不到的东西.

What I have already read:
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)
... and more I couldn't find again.

推荐答案

垃圾收集很复杂,不同的平台实施方式也不同.实际上,同一平台的不同 version 实施垃圾收集的方式有所不同. (还有更多……)

Garbage collection is complicated, and different platforms implement it differently. Indeed, different versions of the same platform implement garbage collection differently. (And more ... )

典型的现代收藏家所基于的观察结果是,大多数对象都死于年轻.也就是说,它们在创建后很快就无法访问.然后将堆划分为两个或更多的空间".例如一个年轻"空间和一个旧"空间.

A typical modern collector is based on the observation that most objects die young; i.e. they become unreachable soon after they are created. The heap is then divided into two or more "spaces"; e.g. a "young" space and an "old" space.

  • 在年轻"空间中创建新对象,并经常对其进行收集. 年轻"空间趋向于变小,并且年轻"集合很快发生.
  • 旧"空间是寿命长的对象结束的地方,很少收集该空间.在旧的"空间收集往往更昂贵. (出于各种原因.)
  • 在新"空间中存活了多个GC周期的对象将被保留";也就是说,它们被移到了旧"空间.
  • 有时我们可能会发现需要同时收集新旧空间.这称为完整集合.完整的GC最昂贵,通常会在相对较长的时间内停止运行".

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

(There are all sorts of other clever and complex things ... which I won't go into.)

您的问题是,为什么直到您致电System.gc(),空间使用量才不会显着下降.

Your question is why doesn't the space usage drop significantly until you call System.gc().

答案基本上是,这是一种有效的处理方式.

The answer is basically that this is the efficient way to do things.

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

The real goal of collection is not to free as much memory all of the time. Rather, the goal is to ensure that there is enough free memory when it is needed, and to do this either with minimum CPU overheads or a minimum of GC pauses.

因此,在正常操作中,GC的行为将与上述相同:频繁进行新"空间收集,而不频繁进行旧"空间收集.和收藏将根据需要"运行.

So in normal operation, the GC will behave as above: do frequent "new" space collections and less frequent "old" space collections. And the collectionswill run "as required".

但是,当您调用System.gc()时,JVM通常 尝试获取尽可能多的内存.这意味着它会执行完整gc"操作.

But when you call System.gc() the JVM will typically try to get back as much memory as possible. That means it does a "full gc".

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

Now I think you said it takes a couple of System.gc() calls to make a real difference, that could be related to use of finalize methods or Reference objects or similar. It turns out that finalizable objects and Reference are processed after the main GC has finished by a background thread. The objects are only actually in a state where they can be collected and deleted after that. So another GC is needed to finally get rid of them.

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

Finally, there is the issue of the overall heap size. Most VMs request memory from the host operating system when the heap is too small, but are reluctant to give it back. The reason is that collectors are generally most efficient if the proportion of garbage to non-garbage is high. By giving emptied memory back to the OS, a JVM is going to impact performance latter ... if the heap grows again.

我不确定Android收集器,但是Oracle收集器会在连续的完整"收集结束时注意到可用空间比率.如果在给定的循环次数后可用空间比率过高",它们只会减小堆的总体大小.

I'm not sure about the Android collector, but the Oracle collectors note the free space ratio at the end of successive "full" collections. They only reduce the overall size of the heap if the free space ratio is "too high" after a given number of cycles.

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

Assuming that the Android collector works the same way, that is another explanation for why you had to run System.gc() multiple times to get the heap size to shrink.

这篇关于垃圾收集器无法释放"thrash内存"就像在Android应用程序中一样的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 14:19
查看更多