Android app out of memory issues - tried everything and still at a loss
Asked Answered
E

10

92

I spent 4 full days trying everything I can to figure out the memory leak in an app I'm developing, but things stopped making sense a long time ago.

The app I'm developing is of social nature, so think profile Activities (P) and list Activities with data - for example badges (B). You can hop from profile to a badge list to other profiles, to other lists, etc.

So imagine a flow like this P1 -> B1 -> P2 -> B2 -> P3 -> B3, etc. For consistency, I'm loading profiles and badges of the same user, so each P page is the same and so is each B page.

The general gist of the problem is: after navigating for a bit, depending on the size of each page, I get an out-of-memory exception in random places - Bitmaps, Strings, etc - it doesn't seem to be consistent.

After doing everything imaginable to figure out why I am running out of memory, I have come up with nothing. What I don't understand is why Android isn't killing P1, B1, etc if it runs out of memory upon loading and instead crashes. I would expect these earlier activities to die and be resurrected if I ever Back to them via onCreate() and onRestoreInstanceState().

Let alone this - even if I do P1 -> B1 -> Back -> B1 -> Back -> B1, I still get a crash. This indicates some sort of a memory leak, yet even after dumping hprof and using MAT and JProfiler, I can't pinpoint it.

I've disabled loading of images from the web (and increased the test data loaded to make up for it and make the test fair) and made sure the image cache uses SoftReferences. Android actually tries to free up the few SoftReferences it has, but right before it crashes out of memory.

Badge pages get data from the web, load it into an array of EntityData from a BaseAdapter and feed it to a ListView (I'm actually using CommonsWare's excellent MergeAdapter, but in this Badge activity, there is really only 1 adapter anyway, but I wanted to mention this fact either way).

I've gone through the code and was not able to find anything that would leak. I cleared and nulled everything I could find and even System.gc() left and right but still the app crashes.

I still don't understand why inactive activities that are on the stack don't get reaped, and I'd really love to figure that out.

At this point, I'm looking for any hints, advice, solutions... anything that could help.

Thank you.

Elfrieda answered 24/9, 2011 at 4:18 Comment(1)
So, if I have opened up 15 activities in the last 20 seconds(user is going through very fast), could that be the problem? What line of code should I add to clear the activity after it has displayed? I get an outOfMemory error. THanks!Sabina
E
2

One of the things that really helped the memory issue in my case ended up being setting inPurgeable to true for my Bitmaps. See Why would I ever NOT use BitmapFactory's inPurgeable option? and the answer's discussion for more info.

Dianne Hackborn's answer and our subsequent discussion (also thanks, CommonsWare) helped clarify certain things I was confused about, so thank you for that.

Elfrieda answered 1/11, 2011 at 1:36 Comment(1)
I know this is an old topic but I see seem to find this thread on many searches. I want to link a great tutorial I learned so much from which you can find here: developer.android.com/training/displaying-bitmaps/index.htmlCubature
V
115

I still don't understand why inactive activities that are on the stack don't get reaped, and I'd really love to figure that out.

This is not how things work. The only memory management that impacts activity lifecycle is the global memory across all processes, as Android decides that it is running low on memory and so need to kill background processes to get some back.

If your application is sitting in the foreground starting more and more activities, it is never going into the background, so it will always hit its local process memory limit before the system ever comes close to killing its process. (And when it does kill its process, it will kill the process hosting all the activities, including whatever is currently in the foreground.)

So it sounds to me like your basic problem is: you are letting too many activities run at the same time, and/or each of those activities is holding on to too many resources.

You just need to redesign your navigation to not rely on stacking up an arbitrary number of potentially heavy-weight activities. Unless you do a serious amount of stuff in onStop() (such as calling setContentView() to clear out the activity's view hierarchy and clear variables of whatever else it may be holding on to), you are just going to run out of memory.

You may want to consider using the new Fragment APIs to replace this arbitrary stack of activities with a single activity that more tightly manages its memory. For example if you use the back stack facilities of fragments, when a fragment goes on the back stack and is no longer visible, its onDestroyView() method is called to completely remove its view hierarchy, greatly reducing its footprint.

Now, as far as you crashing in the flow where you press back, go to an activity, press back, go to another activity, etc and never have a deep stack, then yes you just have a leak. This blog post describes how to debug leaks: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html

Vacant answered 27/9, 2011 at 22:11 Comment(24)
In the OP's defense, this isn't what the documentation suggests. Quoting developer.android.com/guide/topics/fundamentals/… "(When an activity is stopped), it is no longer visible to the user and it can be killed by the system when memory is needed elsewhere." "If an activity is paused or stopped, the system can drop it from memory... by asking it to finish (calling its finish() method)" "(onDestroy() is called) because the system is temporarily destroying this instance of the activity to save space."Storage
On the same page, "However, when the system destroys an activity in order to recover memory". What you are indicating is that Android never destroys activities to reclaim memory, but will only terminate the process to do so. If so, this page needs a serious rewrite, as it repeatedly suggests that Android will destroy an activity to reclaim memory. Also note that many of the quoted passages also exist in the Activity JavaDocs.Storage
Thanks Dianne and Mark. That is what misled me and made me spend hours trying to get it to happen. There is a surprisingly low amount of correct information on this issue on the web. But I arrived at the same conclusion that Android will not destroy activities that are part of an active process - something that would have actually made a lot of sense. Those activities, if one were to come back to them, would just run through restore/create paths.Elfrieda
Dianne, without going to a whole new paradigm and rewriting the whole app (which is already late) to use Fragments, is there something you can recommend that could simulate an activity manager that detects memory is running low in the current activity's heap and then starts killing off (without removing from the back stack) activities that are still running, starting from the first one? A pseudo activity manager of sorts that is not afraid to get dirty with a running process.Elfrieda
No, there is no way to force the activity manager to clean out activities like that.Vacant
I thought I had put this here, but I posted a request to update the documentation for this: code.google.com/p/android/issues/detail?id=21552Expel
Almost a year later, docs are still very misleading: developer.android.com/guide/components/… says "However, [a stopped activity] is no longer visible to the user and it can be killed by the system when memory is needed elsewhere."Euonymus
It's 2013 and the docs have only come to present the false point more clearly: developer.android.com/training/basics/activity-lifecycle/…, "Once your activity is stopped, the system might destroy the instance if it needs to recover system memory. In EXTREME cases, the system might simply kill your app process"Grouper
Well, crap. I definitely always thought (because of the docs) that the system managed the stopped activities in your process and would serialize and deserialize (destroy and create) them as needed.Redletter
Very misleading, even books are propagating the misconception... with nice, convincing pictures techotopia.com/index.php/….Fadeout
@Storage @Vacant Then why is there a flag ``Don't Keep Activity` in the developer options. I believed it is there to test this very scenario??Sedimentology
@unrealsoul007: "Then why is there a flag ``Don't Keep Activity` in the developer options" -- to force you to test the behavior that you get from configuration changes (e.g., screen rotations).Storage
@Storage Thanks for answering my query. I am trying to explain this to my peers but they strongly believe Android can reap activities that are in background as it is written in the android docs.. Don't know how to go about it.. :/Sedimentology
@unrealsoul007: "but they strongly believe Android can reap activities that are in background as it is written in the android docs" -- your peers are welcome to write a demonstration app that demonstrates what they think the behavior is. Wish them good luck with that for me. Considering that Dianne Hackborn is one of the core Android engineers, I am skeptical that your peers will have much success.Storage
@Storage I think these words of yours will be able to convince them. After all, CommonsWare knows Android a little. Thanks!!Sedimentology
@Storage "to force you to test the behavior that you get from configuration changes (e.g., screen rotations)" - Wouldn't it be simpler to just rotate the screen?Bronchiectasis
@KevinKrumwiede: Simpler? Yes. That's what I do, mostly. However, nobody is forcing you to rotate the screen, reminding you to rotate the screen, etc.Storage
@Storage I think this demonstrates the difference between specification and implementation. Even if Android doesn't actually reap activities in the backstack, because the docs say it may, you must write your apps as if it will. That's what "don't keep activities" is for.Bronchiectasis
@Storage So, if I have opened up 15 activities in the last 20 seconds(user is going through very fast), could that be the problem? What line of code should I add to clear the activity after it has displayed? I get an outOfMemory error. Thanks!Sabina
Now seems Android OS may kill individual activity or even the whole process, so the doc is right in today's perspective. Powered by our online experience.Sammy
@Sammy is there a reference for this?Duplicature
@WeishiZeng maybe Stop actSammy
This is actually not true, I've just reproduced it on 2.3. I was starting ~10 new activities in a row, allocating int[1005000] array in each of those. And before I got OOM, I had 2 previous activities destroyedSandblind
The official doc is not misleading anymore : Activity state and ejection from memory states clearly that "the system never kills an activity directly to free up memory. Instead, it kills the process in which the activity runs"Fly
A
23

Some tips:

  1. Make sure you are not leak activity context.

  2. Make sure you are don't keep references on bitmaps. Clean all of your ImageView's in Activity#onStop, something like this:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. Recycle bitmaps if you don't need them anymore.

  4. If you use memory cache, like memory-lru, make sure it is not using to much memory.

  5. Not only images take alot of memory, make sure you don't keep too much other data in memory. This easily can happens if you have infinite lists in your app. Try to cache data in DataBase.

  6. On android 4.2, there is a bug(stackoverflow#13754876) with hardware acceleration, so if you use hardwareAccelerated=true in your manifest it will leak memory. GLES20DisplayList - keep holding references, even if you did step (2) and no one else is referencing to this bitmap. Here you need:

    a) disable hardware acceleration for api 16/17;
    or
    b) detach view that holding bitmap

  7. For Android 3+ you can try to use android:largeHeap="true" in your AndroidManifest. But it will not solve your memory problems, just postpone them.

  8. If you need, like, infinite navigation, then Fragments - should be your choice. So you will have 1 activity, which will just switch between fragments. This way you will also solve some memory issues, like number 4.

  9. Use Memory Analyzer to find out the cause of your memory leak.
    Here is very good video from Google I/O 2011: Memory management for Android Apps
    If you dealing with bitmaps this should be a must read: Displaying Bitmaps Efficiently

Astragalus answered 15/1, 2013 at 6:34 Comment(1)
So, if I have opened up 15 activities in the last 20 seconds(user is going through very fast), could that be the problem? What line of code should I add to clear the activity after it has displayed? I get an outOfMemory error. THanks!Sabina
R
4

Bitmaps are often the culprit for memory errors on Android, so that would be a good area to double check.

Roxannroxanna answered 25/9, 2011 at 3:40 Comment(1)
So, if I have opened up 15 activities in the last 20 seconds(user is going through very fast), could that be the problem? What line of code should I add to clear the activity after it has displayed? I get an outOfMemory error. THanks!Sabina
G
2

Are you holding some references to each Activity? AFAIK this is a reason which keeps Android from deleting activities from the stack.

We're you able to reproduce this error on other devices as well? I've experienced some strange behaviour of some android devices depending on the ROM and/or hardware manufacturer.

Grinnell answered 24/9, 2011 at 5:31 Comment(8)
I was able to reproduce this on a Droid running CM7 with max heap set to 16MB, which is the same value I'm testing on the emulator.Elfrieda
You may be onto something. When the 2nd activity starts, will the 1st one do onPause->onStop or just onPause? Because I'm printing all on******* lifecycle calls, and I'm seeing onPause -> onCreate, without onStop. And one of the crash dumps actually said something like onPause=true or onStop=false for like 3 activities that it was killing.Elfrieda
OnStop should be called when the activity leaves the screen but may not be if it is reclaimed by the system early.Tress
It doesn't get reclaimed because I don't see onCreate and onRestoreInstanceState called if I click back.Elfrieda
according to the life cycle those might never be called though, check my answer that has a link to the official developer blog, it's more than likely the way you're passing your bitmaps aroundTress
Why are you running a Droid with max heap set to 16MB? To be a compatible device, a device with an hdpi screen must have a max heap of at least 24MB (and in GB 32MB) to account for the increased size of bitmaps.Vacant
@Vacant Dianne, I set it to 16MB to simulate low-memory conditions faster. The issue occurs on the Nexus S as well, just needs more time.Elfrieda
If it is a matter of how much time you have, then it is just a memory leak that you need to debug and remove.Vacant
P
2

I think the problem maybe a combination of many factors stated here in the answers are what is giving you problems. Like @Tim said, a (static) reference to an activity or an element in that activity can cause the GC to skip the Activity. Here is the article discussing this facet. I would think the likely issue comes from something keeping the Activity in an "Visible Process" state or higher, which will pretty much guaranty that the Activity and its associated resources never get reclaimed.

I went through the opposite problem a while back with a Service, so that's what got me going on this thought: there is something keeping your Activity high on the process priority list so that it won't be subject to the system GC, such as a reference (@Tim) or a loop (@Alvaro). The loop doesn't need to be an endless or long running item, just something that runs a lot like a recursive method or cascaded loop (or something along those lines).

EDIT: As I understand this, onPause and onStop are called as needed automatically by Android. The methods are there mainly for you to overide so that you can take care of what you need to before the hosting process is stopped (saving variables, manually saving state, etc.); but note that it is clearly stated that onStop (along with onDestroy) may not be called in every case. Additionally, if the hosting process is also hosting an Activity, Service, etc. that has a "Forground" or "Visible" status, the OS might not even look at stopping the process/thread. For example: an Activity and a Service are both luanched in the same process and the Service returns START_STICKY from onStartCommand() the process automatically takes at least a visible status. That might be the key here, try declaring a new proc for the Activity and see if that changes anything. Try adding this line to the declaration of your Activity in the Manifest as: android:process=":proc2" and then run the tests again if your Activity shares a process with anything else. The thought here is that if you've cleaned up your Activity and are pretty sure that the problem is not your Activity then something else is the problem and its time to hunter for that.

Also, I can't remember where I saw it (if I even saw it in the Android docs) but I remember something about a PendingIntentreferencing an Activity may cause an Activity to behave this way.

Here is a link for the onStartCommand() page with some insights on the process non-killing front.

Present answered 24/9, 2011 at 6:52 Comment(1)
Based on the link you've provided (thank you), an activity can be killed if its onStop has been called, but in my case, I think something is preventing onStop from running. I'll definitely look into why. However, it also says activities with only onPause can be also killed, which is not happening in my case (I see onPause getting called but not onStop).Elfrieda
E
2

One of the things that really helped the memory issue in my case ended up being setting inPurgeable to true for my Bitmaps. See Why would I ever NOT use BitmapFactory's inPurgeable option? and the answer's discussion for more info.

Dianne Hackborn's answer and our subsequent discussion (also thanks, CommonsWare) helped clarify certain things I was confused about, so thank you for that.

Elfrieda answered 1/11, 2011 at 1:36 Comment(1)
I know this is an old topic but I see seem to find this thread on many searches. I want to link a great tutorial I learned so much from which you can find here: developer.android.com/training/displaying-bitmaps/index.htmlCubature
S
1

so the only thing i can really think of is if you have a static variable that references directly or indirectly to the context. Even something so much as a reference to part of the application. I'm sure you have already tried it but i will suggest it just in case, try just nulling out ALL of your static variables in the onDestroy() just to make sure the garbage collector gets it

Silures answered 24/9, 2011 at 5:25 Comment(0)
Y
1

The biggest source of memory leak I have found was caused by some global, high level or long-standing reference to the context. If you are keeping "context" stored in a variable anywhere, you may encounter unpredictable memory leaks.

Youmans answered 24/9, 2011 at 6:34 Comment(2)
Yeah, I heard and read this many times, but I've had trouble pinning it down. If only I could reliably figure out how to trace those.Elfrieda
In my case this translated into any any variable at class level that was set equal to context (e.g. Class1.variable=getContext();). In general, replacing every usage of "context" in my app with a fresh call to "getContext" or similar solved my biggest memory problems. But mine were unstable and erratic, not predictable like in your case, so possibly something different.Youmans
F
1

Try passing getApplicationContext() to anything that needs a Context. You might have a global variable that is holding a reference to your Activities and preventing them from being garbage collected.

Fye answered 27/9, 2011 at 23:39 Comment(0)
K
0

I encountered the same problem with you. I was working on a instant messaging app, for the same contact, it is possible to start a ProfileActivity in a ChatActivity, and vice versa. I just add a string extra into the intent to start another activity, it takes the information of class type of starter activity, and the user id. For example, ProfileActivity starts a ChatActivity, then in ChatActivity.onCreate, I mark the invoker class type 'ProfileActivity' and user id, if it's going to start an Activity, I would check whether it is a 'ProfileActivity' for the user or not. If so, just call 'finish()' and go back to the former ProfileActivity instead of creating a new one. Memory leak is another thing.

Kayne answered 29/10, 2013 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.