为什么重新启动 Activity 时堆内存会增加?
这个问题涉及 Android 中的内存。
我的方法:
我有两个活动,A和B。从A,我像这样启动B:
Intent i = new Intent(A.this, B.class);
startActivity(i);
在B中单击按钮时,我这样做:
B.this.finish();
- 在B中,我重写onDestroy方法并设置所有引用为空。
- 我没有在 A 的 onResume 方法中分配新内存。
- 我没有泄漏上下文。
- 我没有使用多线程。
- 我没有使用服务。
- B 中的所有变量都是私有类变量,并且在 B 的 onDestroy 中全部设置为 null。
- 此外,B 中的 ImageView 在 B 的 onDestroy 中将其背景设置为 null。
- 我确信 B 会被销毁。
结果:
当我处于活动 A 时,堆内存为 7.44 MB。然后,当我启动 B 并在 B 上调用 finish(从而返回到 A)时,堆增加了 0.16 MB。再次重复此过程,堆每次增加 0.08 MB。
- 我不是在查看堆限制,而是在查看分配的堆。
- 我在 B 的 onDestroy 方法末尾调用 System.gc()。
其他信息:
-我使用 MAT 来分析内存分配并尝试找到此泄漏。奇怪的是,Activity B 似乎有 5 个实例。碰巧的是,我重复了 startActivity/finish 过程 5 次。底部条目是 Activity,其他条目是 Activity 中的侦听器:
这是统治者的屏幕截图树。我找不到任何异常或可疑的东西。
-我观看了两个有关内存使用(和泄漏)的 google IO 视频。
问题:
是否有可能无论我做什么,这 0.08 MB 的堆将始终被分配(并且不能被 GC 回收)?如果不是,知道可能是什么原因造成的吗?
更新:
我尝试启动 Activity B,但未在 B 中设置内容视图。这意味着 B 是一个完全空的 Activity。结果是,当我多次重新启动活动时,堆内存没有增加。但请注意,这不是解决方案。我必须能够设置内容视图。
scorpiodawg:我尝试在模拟器上运行我的应用程序,堆仍然增长。不过,很好的尝试。
ntc:我将所有出现的“this”更改为“getApplicationContext()”(如果可能)。我无法调用 setContentView(getApplicationContext());因为 setContentView 想要引用布局文件,而不是上下文。我所做的是创建一个空布局文件并调用 setContentView(emptylayout);在 Activity B 的 onDestroy 方法中。这没有帮助。
我尝试删除所有代码,以便仅调用 setContentView(mylayout) 。问题仍然存在。然后我删除了布局 XML 文件中的所有 gui 元素。问题仍然存在。唯一剩下的就是容器视图,几个嵌套的线性布局、相对布局和滚动布局。我尝试删除滚动条中“android:scrollbarDefaultDelayBeforeFade”属性的设置。结果很好,内存泄漏消失了。然后我放回了之前删除的所有代码,但没有设置“android:scrollbarDefaultDelayBeforeFade”属性,内存泄漏又回来了。这有多奇怪?
This question concerns memory in Android.
My method:
I have two activites, A and B. From A, I launch B like this:
Intent i = new Intent(A.this, B.class);
startActivity(i);
On button click in B, I do this:
B.this.finish();
- In B, I override the onDestroy method and set all references to null.
- I do not allocate new memory in the onResume method of A.
- I am not leaking a context.
- I am not using multiple threads.
- I am not using services.
- All variables in B are private class variables, and all of them are set to null in the onDestroy of B.
- Also, ImageViews in B have their background set null in the onDestroy of B.
- I am certain that B gets destroyed.
The result:
When I am in Activity A, heap memory is at 7.44 MB. Then when I start B and call finish on B(and thus returning to A), heap is increased by 0.16 MB. Repeating this process again, heap is increased by 0.08 MB every time.
- I'm not looking at the heap limit, I'm looking at the allocated heap.
- I'm calling System.gc() at the end of the onDestroy method of B.
Additional info:
-I have used MAT to analyse memory allocations and try to find this leak. Something strange is that Activity B seems to have 5 instances. As it so happens, I was repeating the startActivity/finish process 5 times. The bottom entry is the Activity, the others are listeners in the activity:
And this is screenshot of the dominator tree. I can't find anything unusual or suspect.
-I have watched both google IO videos on memory usage(and leaks).
Question:
Is it possible that this 0.08 MB of heap will always be allocated(and not collectable by the GC) no matter what I do? If not, any idea of what might be causing this?
Update:
I tried to launch activity B without setting a content view in B. This means that B is a completely empty activity. The result was that the heap memory did NOT increase when I'm relaunching the activity several times. Note, however, that this is no solution. I must be able to set a content view.
scorpiodawg: I tried running my app on an emulator, and the heap still grows. Good try though.
ntc: I changed all occurences of "this" to "getApplicationContext()" where it was possible. I could not call setContentView(getApplicationContext()); because setContentView wants a reference to a layout file, not a context. What I did instead was to create an empty layout file and call setContentView(emptylayout); in the onDestroy method of Activity B. That did not help.
I tried to remove all the code so that only setContentView(mylayout) gets called. Problem persisted. Then I removed all the gui elements in the layout XML file. Problem persisted. The only thing that was left was the container views, a couple of nested linear, relative and scrolllayouts. I tried to remove setting the "android:scrollbarDefaultDelayBeforeFade" attribute in the scrollbar. The result was great, the memory leak had vanished. Then I put back all the code I previously removed but didn't set the "android:scrollbarDefaultDelayBeforeFade" attribute and the memory leak was back. How strange is that?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
考虑
在 AndroidManifest.xml 中使用。 查看此内容。
Consider using
in your AndroidManifest.xml. See this.
这0.08MB的堆如果没有使用,会在系统认为需要的时候被回收。垃圾收集不会在您调用 System.gc() 时立即发生,它更像是请求 GC 尽快发生。对象被分配和从内存中删除之间通常有长的时间。 Java 虚拟机规范和 Java 语言规范指定了经历以下阶段的对象的生命周期:
请注意,当对象不可达时,它仅成为垃圾收集的候选者,如下所示以及何时可能发生。
即使使用像 Dalvik 这样占用空间很小的 JVM,我也不认为一个对象会在这么短的时间内经历完整的生命周期。 GC 是一项昂贵的操作,因此仅在需要时才执行。
如果您有需要快速从内存中删除的对象(出于某种原因),请尝试 WeakReference 。或者,也许您可以提供更多关于您正在尝试做的事情的背景信息,这样有人就可以更好地帮助您。
有关 Java 中 GC 如何发生的更多信息,
这是一篇非常好的文章
The 0.08MB of heap, if unused, will be reclaimed when the system considers that it needs it. Garbage Collection does not happen as soon as you call System.gc(), it more like a request for GC to happen as soon as it is possible. There is often a LONG time between an object being allocated and it being removed from memory. The Java Virtual Machine Specification and Java Language Specification specify the lifetime of the object undergoing the following stages:
Note that when an object is Unreachable, it merely becomes a candidate for garbage collection, as and when it may happen.
Even with a small footprint JVM like Dalvik, I don't think an object will undergo the full lifetime in a span of time that short. GC is an expensive operation, so is done only as when its needed.
If you have objects that need to be removed from memory quickly (for some reason), then try WeakReference . Or maybe you could provide some more context as to what you're trying to do, and someone could be able to help you out better that way.
For more info on how GC happens in Java,
this is a pretty good article
如果您有 5 个活动 B 实例,则说明您没有正确管理活动堆栈。
我发现检查它的最佳方法是使用 CLI 命令:
adb shell dumpsys meminfo 'your apps package name'
我在两个活动项目中遇到了类似的问题,当我他们之间切换。每次切换时,我都会在堆栈上得到一个新实例,如上述命令所示。然后,我将已启动活动的标志设置为 FLAG_ACTIVITY_REORDER_TO_FRONT,代码如下:
完成此操作后,当我在两个活动之间切换时,adb shell 命令不会显示两个活动的更多实例
If you have 5 instances of activity B, then you are not managing the activity stack correctly.
I find the best way to check it is with the CLI command:
adb shell dumpsys meminfo 'your apps package name'
I had a similar problem in a two activity project when I switched between them. Every time I switched I got a new instance on the stack as revealed by the above command. I then set the flags for the launched activities to FLAG_ACTIVITY_REORDER_TO_FRONT with code like:
Once I'd done this, then the adb shell command did not show more instances of my two activities when I switched between them
看来您在该活动中存在内存泄漏。
可能您正在泄漏上下文(本例中的活动)。因此,请确保在活动中调用 onDestroy 方法时清除对上下文的所有引用。 更多详细信息请参见此处。
另请注意,当您完成活动时,可能的内容观察者尚未取消注册。
Seems that you have a memory leak within that activity.
Probably you are leaking the context (the activity in this case). So ensure that all the references to the context are being cleaned when you call the onDestroy method in the activity. More details here.
Also take a look to possible content observers not being unregistered when you finish the activity.
我认为这是一个典型的java问题。杀死一个 Activity 并不意味着它的关联对象应该从堆中删除,即使它们位于失落的土地上(它们的引用为空)。因为它在调用垃圾收集器时完全依赖于虚拟机(不管你说的是System.GC())。因此,当出现内存几乎耗尽的情况时,它会调用它并清除它(无法再次确定,可能是在它们的引用变为
null
之后立即),所以我认为您不应该担心关于它。编辑:
调用
setContentView(getApplicationContext);
,无论您在哪里使用
this
关键字传递上下文,请将其更改为this.getApplicationContext()
What i think is, its a typical java question. And killing an Activity does't mean that its associated objects should be removed from the
heap
, even if they are in lost land (their reference are null). because its completely dependent on virtual machine when it callsGarbage collector
(Irrespective of you sayingSystem.GC()
). so when there the condition is like nearly out of memory it calls it and cleans it(cannot be sure again, may be immediately after their reference becomesnull
), So i don't think you should be worrying about it.edited:
call
setContentView(getApplicationContext);
and where ever you are
this
keyword to pass a context, change it tothis.getApplicationContext()