How to Find Unwanted References in Android Using Android Profiler
Asked Answered
C

1

7

I'm trying to get better at hunting down memory leaks in Android.

I have discovered the Android Profiler and learned how to perform a heap dump and how to determine if there are too many instances of a given object in memory.

I have read that one of the ways to get to the root of why an unwanted object is still hanging around is to highlight it and "look at what objects are still holding references to it and trace your way back to the original cause."

So... in this screenshot you can see the undesirable situation: I have three instances of MainActivity... and all three of them have a number in the "depth" column signaling they are really leaks.

If the object in question were a class of my own creation, then the process would be more straight-forward, but since we're dealing with an actual Activity here, when I highlight any one of the three, there is a massive list of objects referencing it (the list goes far beyond the screenshot).

Surely most of these are normal/benign references -- How am I supposed to tell which ones are worth investigating?

What are the clues? Is it the " this$0 " ? Or the massive number in the retained column? A depth number matching the object in question? I'm just guessing at this point.

Surely I'm not expected to go through the entire list mulling to myself, "Nope... can't be that one... that's a normal part of Android Framework X,Y, and Z..."

enter image description here

Critic answered 14/8, 2018 at 12:20 Comment(2)
try using this library to detect memory leaks it give you exactly where the leak is happening and the cause as well github.com/square/leakcanaryBrenza
Thanks I've installed it and it's definitely helpful... I would still appreciate any advice regarding the original question if anyone out there is interested.Critic
H
8

I'm trying to get better at hunting down memory leaks in Android.

I will give couple of examples but first, due to the number of questions you asked that is not related to your topic I will first explain what you see in "Instance view";

  • Depth: The shortest number of hops from any GC root to the selected instance.

    • 0 is for "private final class" or static members
    • if it's blank it's going to be reclaimed - which is harmless for you
  • Shallow Size: Size of this instance in Java memory

  • Retained Size: Size of memory that this instance dominates (as per the dominator tree).

Is it the this$0?

this$0 is a synthetic variable generated by the compiler which means "one level out of my scope", it's a parent object of a non-static inner class.

How am I supposed to tell which ones are worth investigating?

It depends, if the depth is 0 and it's your code - investigate, maybe it's a long running task with bad end condition.

When analyzing a heap dump in the Memory Profiler, you can filter profiling data that Android Studio thinks might indicate memory leaks for Activity and Fragment instances in your app.

The types of data that the filter shows include the following:

  • Activity instances that have been destroyed but are still being referenced.

  • Fragment instances that do not have a valid FragmentManager but are still being referenced.

In certain situations, such as the following, the filter might yield false positives:

  • A Fragment is created but has not yet been used.

  • A Fragment is being cached but not as part of a FragmentTransaction.

Filtering a heap dump for memory leaks.

Techniques for profiling your memory

While using the Memory Profiler, you should stress your app code and try forcing memory leaks.

One way to provoke memory leaks in your app is to let it run for a while before inspecting the heap.

Leaks might trickle up to the top of the allocations in the heap.

However, the smaller the leak, the longer you need to run the app in order to see it.

You can also trigger a memory leak in one of the following ways:

  • Rotate the device from portrait to landscape and back again multiple times while in different activity states. Rotating the device can often cause an app to leak an Activity, Context, or View object because the system recreates the Activity and if your app holds a reference to one of those objects somewhere else, the system can't garbage collect it.
  • Switch between your app and another app while in different activity states (navigate to the Home screen, then return to your app).

A growing graph is a big indicator

If you observe a trend line that only keeps going up and seldomly goes down, it could be due to a memory leak, which means some pieces of memory cannot be freed. Or there’s simply not enough memory to cope with the application. When the application has reached its memory limit and the Android OS isn’t able to allocate any more memory for the application, an OutOfMemoryError will be thrown.

Turbulence within a short period of time

Turbulence is an indicator of instability, and this applies to Android memory usage as well. When we observe this kind of pattern, usually a lot of expensive objects are created and thrown away in their short lifecycles.

The CPU is wasting a lot of cycles in performing Garbage Collection, without performing the actual work for the application. Users might experience a sluggish UI, and we should definitely optimize our memory usage in this case.

If we are talking about Java memory leaks enter image description here

public class ThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);

        new DownloadTask().start();
    }

    private class DownloadTask extends Thread {
        @Override
        public void run() {
           SystemClock.sleep(2000 * 10);
        }
    }
}

Inner classes hold an implicit reference to their enclosing class, it will generate a constructor automatically and passes the activity as a reference to it.

The above code is actually

public class ThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);

        new DownloadTask(this).start();
    }

    private class DownloadTask extends Thread {

        Activity activity;

        public DownloadTask(Activity activity) {
            this.activity = activity;
        }

        @Override
        public void run() {
            SystemClock.sleep(2000 * 10);
        }
    }
}

In a normal case, the user opens the activity wait 20sec until the download task is done.

When the task is done the stack released all the objects.

Then the next time the garbage collector works he will release the object from the heap.

And when the user closes the activity the main method will be released from the stack and the ThreadActivity also reclaimed from the heap and everything works as needed without leaks.

In a case that the user closes/rotate the activity after 10sec.

The task is still working that means the reference for the activity still alive and we have a memory leak 💣

Note: when the download run() task is done the stack release the objects. so when the garbage collector works next time the objects will be reclaimed from the heap because there is no object referenced on it.

related Youtube playlist

And https://square.github.io/leakcanary/fundamentals/ is great.

Hollow answered 20/4, 2020 at 18:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.