0

I know this may seem impossible to determine without actual code but please bear with me. I think it's possible to determine something from the symptoms.

I have a hierarchy browser where each level of a hierarchy is displayed by its own activity. If you examine too many branches of the hierarchy, it will throw an OutOfMemoryError exception. This sounds like a memory leak but a casual look at the code doesn't reveal anything obvious so I want some clarification on some aspects of Java and Android before I do a more thorough investigation.

First, can I rely on unused activities to be garbage collected before OutOfMemoryError is thrown? I would consider it to be odd behavior if this were not the case but it would explain everything I'm encountering.

Secondly, can the Dalvik garbage collector collect circular references? I don't think I have any but if it can't, I'll know to look for that in my code.

I know there are way to leak memory in Android applications by creating references to things from places that live for the life of the program but, again, I don't think I'm doing that. For example, I haven't created any UI controls programatically, nor have I passed the current activity as a Context to anything.

Finally, I've never noticed this bug before recently and the only thing that's changed since it was first created is that the Android version was upgraded from some flavor Jelly Bean to KitKat.

Any help would be greatly appreciated. Thank you!

Jocko Homo
  • 175
  • 4
  • 1
    If you do not call `finish()` or your activities, then you cannot rely on them being GCed since Android is responsible for their life cycle (`onCreate()` vs `onResume()`). An activity which is moved to the background, which you do not `finish()`, may or may not be released and therefore become eligible for GC. From your description, I would suggest that overrriding the back key and finishing your activity is the way to go. All of that said, Android will definitely kill your activities as heap usage nears an OOM condition so yes, I suspect that you are leaking something. Bitmaps? – Simon Dec 27 '13 at 21:06
  • possible duplicate of [Android app out of memory issues - tried everything and still at a loss](http://stackoverflow.com/questions/7536988/android-app-out-of-memory-issues-tried-everything-and-still-at-a-loss) – Darth Beleg Dec 27 '13 at 22:17

1 Answers1

1

First, can I rely on unused activities to be garbage collected before OutOfMemoryError is thrown?

Despite the popular belief, the answer is no. You can fill memory with activities up to the error being thrown and Android will neither destroy any activities in the back stack nor collect these activities during GC.

There was an option for the system to finish activities in the back stack but it was disabled at 2011. Now Android will just kill the entire process if the system memory is low. Foreground application will most likely hit per-application heap limit because system avoids to kill it and there may be a plenty of free memory for others.

Why activities aren't GC'ed? All running activities are stored in the ActivityThread class (which performs all interaction with the system and handles activities' lifecycle events), so they aren't eligible for garbage collection. They are only removed from here after being destroyed.

Secondly, can the Dalvik garbage collector collect circular references?

Yes, it can, just like regular JVM's garbage collector. Otherwise it wouldn't be Java-compatible.

You can check where all memory goes by employing Memory Analyzer tool and other techniques. These tools can show you what objects are on heap and what prevents them from being collected.

I can suggest you to use Fragments whithin a single Activity instead of multiple Activities. Fragments have callbacks that can be used to free view hierarchy when fragment goes off scree (to back stack).

Community
  • 1
  • 1
Darth Beleg
  • 2,657
  • 24
  • 26
  • Is the first cast true even for activities not on the backstack? For example, my hierarchy is typically quite shallow so, suppose we have a root node A with two child nodes B and C. If I start an activity A (showing node A) and then select B, go back to A and then view C, B will not get GC'ed even when I'm out of memory? – Jocko Homo Dec 27 '13 at 23:52
  • No, as soon, as activity is destroyed, it becomes eligible for GC. Could you examine heap dump with memory analyzer to find what holds a reference to the destroyed activity? – Darth Beleg Dec 28 '13 at 08:19
  • I can but nothing is obviously a leak. There are some references that are different between the activity that should be destroyed and the root one, like PhoneWindow, PhoneLayoutInflater, FragmentManagerImpl, ViewStub, FrameLayout, RelativeLayout, GridView, ContextImpl, app.Activity. There may be others but I can't see them just by looking. Does any of this sound familiar? – Jocko Homo Dec 28 '13 at 19:48
  • The classes you mentioned are usually referencing an activity as a Context - actually, activity owns these so something else is holding the activity. Have you tried to use "Show path to GC roots" for Activities that should be collected as described in https://developer.android.com/tools/debugging/debugging-memory.html#HeapDump? Usually it quickly becomes obvious what is holding a reference. – Darth Beleg Dec 28 '13 at 20:45
  • Using this (amazing) tool, I can see that it's being held by a ContextImpl and an ImageAdapter. I'm guessing the ContextImpl is pretty normal. The ImageAdapter is an inner class of the Activity so it's unclear what could be holding on to it. setAdapter() is called on a GridView with it as a parameter but you'd think the GridView would be destroyed with the Activity. Again, I'm assuming circular references are okay... – Jocko Homo Dec 28 '13 at 21:09
  • Actually, there's something referencing the ImageAdapter called "ImageAdapter$2 @ 0x4224c1c8 Thread-1702 Unknown" but I have no idea what this is supposed to mean. – Jocko Homo Dec 28 '13 at 21:14
  • I think I see. There was an anonymous thread that had a reference to the ImageAdapter but it should have died and couldn't be the problem. – Jocko Homo Dec 28 '13 at 21:24
  • If the Thread is completed that should be ok. But there is a caveat for threads that aren't started. Such thread stores a reference to itself in the global ThreadGroup. – Darth Beleg Dec 28 '13 at 21:47
  • 1
    Yeah, all threads are started and they have no loops so they are guaranteed to finish. Actually, I appear to have exactly this problem: http://stackoverflow.com/questions/6445052/android-context-memory-leak-listview-due-to-audiomanager but it has no resolution. – Jocko Homo Dec 28 '13 at 22:01