10

I noticed that my application is leaking memory. This can be seen in DDMS, and I managed to get a OutOfMemoryError.

I found the source of the leak. One of the activities has a thread running in the background. This thread is stopped in onDestroy(). It finishes running, as it can be seen in DDMS.

Now, if thread is started, the leak occurs, Activity is not garbage collected after being destroyed, because it is referenced by the thread. If thread is not started at all, everything is ok.

Here's simple example demonstrating this:

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    volatile boolean finished = false;
    byte[] memoryEater = new byte[4 * 1024 * 1024];

    Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
            while (!finished) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            Log.d(getClass().getName(), "Thread finished");
        }
    });

    @Override
    protected void onDestroy() {
        super.onDestroy();
        finished = true;
    }

    public void startActivity(View view) {
        startActivity(new Intent(this, MainActivity.class));
    }

    public void startThread(View view) {
        thread.start();
    }
}

Add one button for starting new activity and one for starting a thread. Start new activity. After going back, the memory will be cleaned only if thread has not been started.

What is the cause of this behaviour?

Tomasz Niedabylski
  • 5,768
  • 1
  • 18
  • 20
  • Could you please formulate a question? I suspect it is behaving as expected. Every time you send an intent, new activity is created and you don't seem to close the Activity. – Alex Pakka Jul 24 '12 at 22:56
  • You close activities by pressing back. That's when the leak occurs – Tomasz Niedabylski Jul 24 '12 at 23:21
  • You hand out a reference to `this` in `startActivity`. Could this reference be stored somewhere? – A.H. Jul 24 '12 at 23:27

4 Answers4

13

I have just figured out this same problem.

Tomasz, you are on the right track. There is NO bug in DDMS and there is NO memory leak in your program.

The really problem is you are running your program in DEBUG mode (under Eclipse). Somehow when Android is running in DEBUG mode, Threads are not garbage collected even after the run() method has been exited. I guess it is probably Android needs to hold on to the Thread for some debugging features to work.

But if you run you application in RUN mode (still under Eclipse), Thread garbage collection takes place. The Thread will be freed completely and your Activity will be freed completely.

nellute
  • 846
  • 7
  • 5
  • Well, it seems probable. It may keep them for things like thread status. So it's not a bug but a not-so-polished feature :) It could release thread's runnable after all. Thanks Nellute! – Tomasz Niedabylski Oct 10 '12 at 06:48
  • I've checked with Memory Analyzer Tool in Eclipse, and it's true! In debug mode, the thread objects are kept! It caused me thinking a lot! Anyway, thanks for the answer! – Aron Lorincz Feb 10 '14 at 13:58
  • 1
    Here is the official documentation blurb (bottom of page): http://developer.android.com/tools/debugging/index.html It Reads: "The debugger and garbage collector are currently loosely integrated. The VM guarantees that any object the debugger is aware of is not garbage collected until after the debugger disconnects. This can result in a buildup of objects over time while the debugger is connected. For example, if the debugger sees a running thread, the associated Thread object is not garbage collected even after the thread terminates." – ptoinson Sep 16 '14 at 15:45
4

I kept investigating and what I've found is really suprising. It seems there is no real memory leak. It happens only when app is in debugging mode in DDMS.

DDMS seems to somehow hold references to those finished treads, preventing them from being GC-ed. When i disconnect the phone and connect again, I can see that all "leaked" resources have been released.

It looks like a bug in DDMS.

Tomasz Niedabylski
  • 5,768
  • 1
  • 18
  • 20
2

The anonymous runnable class used by the thread would have a reference to the activity ('this'). As the thread is referenced by the activity, and the runnable in the thread references the activity, the GC will never collect either of them.

Try doing something more like this:

private static RunnableClass implements Runnable
{
    @Override
    public void run() {
        while (!finished) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        Log.d(getClass().getName(), "Thread finished");
    }
});

Thread thread = new Thread(new RunnableClass());
Noah
  • 1,966
  • 1
  • 14
  • 29
  • 3
    If neither the activity nor the thread can be reached from the *root*, then both will be gc'd at some time, even if there's a strong circular dependency. (at least in jvms, correct me if this is wrong for android/dalvik) – Andreas Dolk Jul 24 '12 at 23:02
  • Activity references thread and vice versa but this should not be a priblem fot gc. They both should not be accessible from any root. Thread is not running and is not a root – Tomasz Niedabylski Jul 24 '12 at 23:19
  • You are correct, if the thread is running, then it would be a root and nothing would get collected. If the thread is not running, there must be a reference to activity somewhere else if it is being retained. – Noah Jul 25 '12 at 13:39
0

Activities are not destroyed when "back" is pressed or any other intent is sent to remove it from the top of the stack. I don't think your overriden onDestroy() method is ever called until your Android OS runs out of memory. From the documentation one can extract:

As discussed in the following section about the activity lifecycle, the Android system manages the life of an activity for you, so you do not need to finish your own activities. Calling these methods could adversely affect the expected user experience and should only be used when you absolutely do not want the user to return to this instance of the activity.

Normally, every Activity instance is laying around in memory after initial creation and goes through onStart()...onStop() cycle without being destroyed. Implement onStop() and call finish() in it for MainActivity with the Thread to be released and consequently garbage collected.

UPDATE: The statement above is not true. Based on the code provided in the question, there is no reason the Activity should not be GC-ed.

Alex Pakka
  • 9,466
  • 3
  • 45
  • 69
  • This is just not true. OnDestroy is always called when Activity finishes. Thread finishes, you can see this in logcat and ddms – Tomasz Niedabylski Jul 25 '12 at 05:34
  • IT is a very bad idea to call `finish()` in `onStop()`. Among other tings, it will finish you app when user cliks home button. Besides, as said in doc snippet you posted, it is generally not a good idea to call `finish()` – Tomasz Niedabylski Jul 27 '12 at 06:28
  • @Tomasz, I agree with you. I should have had my Android setup handy and check things before answering. However, I think it makes sense to keep this post around, even if it is wrong - many people fall for it when figuring out View lifecycle. I will just update it. – Alex Pakka Jul 27 '12 at 21:20