What are memory leaks in android, how to check the program for their absence and how to prevent their occurrence

  • Tutorial
In this article for beginner android developers, I will try to talk about what “memory leaks” are in android, why you should think about them on modern devices that allocate 192 MB per application, how to quickly find and fix these leaks in an unfamiliar application and on that you need to pay special attention when developing any application.


The ultimate goal of this article is to answer a simple question:
Where to click to find out which line in the appendix to fix?

What is a memory leak?


Let's start with what is called a "memory leak." In a strict understanding, an object can be called a memory leak if it continues to exist in memory even after all references to it are lost. The problem immediately arises with this definition: the memory for all objects that you create is allocated with the participation of the garbage collector, and the garbage collector remembers all created objects, regardless of whether you have a reference to the object or not.

In fact, the garbage collector is built very primitively (actually not - but the principle of operation is really simple): there is a graph in which every existing object is a vertex, and a link from any object to any other object is an edge. Some vertices on this graph are special. These are the roots of the garbage collection roots - those entities that are created by the system and continue to exist regardless of whether other objects refer to them or not. If and only if there is any path on the graph from a given object to any root, the object will not be destroyed by the garbage collector.

This is the problem - if the object is not destroyed, then there is a chain of links from the root to this object (or, if such a chain does not exist, the object will be destroyed during the next garbage collection), which means that no object can be a leak memory in the strict sense of the term. Actually, even the fact that the garbage collector itself stores a link to every existing object in the system is already enough.

Attempts to get a “clean” memory leak in java have been made repeatedly and, of course, continue to be made, however, none of the methods can make the garbage collector forget the object reference without freeing up memory. There are memory leaks associated with native code allocation of memory (JNI), however we will not consider them in this article.

Conclusion: you may lose all links to the object you are interested in, but the garbage collector remembers.

So, the definition of “memory leak” in the strict sense does not suit us. Therefore, we will further understand a memory leak as an object that continues to exist after it must be destroyed.

Next, I will show some of the most common cases of memory leaks, show how to detect them and how to avoid them. If you learn to fix these typical memory leaks, then with a probability of 99.9%, your application will not have memory leaks that you need to worry about.

But, before proceeding to a description of these common errors, the main question must be answered: is it necessary to correct these errors at all? The application is working ...

Why spend time troubleshooting memory leaks?



Applications have not crashed for a long time because you forgot to squeeze resources into the drawable-ldpi folder. Preparing to write this article, I conducted a simple experiment: I took one of the working applications and added a memory leak to it in such a way that no activity created was ever unloaded from memory (I began to add them to the static list). I opened the application and started to click on the screens, waiting for the application to finally crash on my Nexus 5. Finally, after 5 minutes and 55 screens, the application crashed. The irony is that, according to Google Analytics, usually a user visits 3 screens per session.

So do you need to worry about memory leaks if the user might just not notice them? Yes, and there are three reasons why.

At firstIf something works in your application that should not work, this can lead to very serious and difficult to debug problems.

For example, you have developed an application for a social network. In this application, you can exchange messages between users, where on the messaging screen there is a timer that makes a request to the server every 10 seconds in order to receive new messages, but you forgot to turn off this timer when you exit the screen. What will this lead visually? Yes to anything. You will not notice that the application is doing something wrong. But at the same time, the application will continue to send a request to the server every 10 seconds. Even after you exit the application. Even after you turn off the screen (behavior may vary by phone). If the user enters the screens of communication with three different friends, within an hour you will receive 1000 extra requests to the server and one user who is very angry with your application, which consumes battery power intensively.

You may argue that this is not a memory leak, but just a timer not turned off, and this is a completely different error. It does not matter. It is important that by checking your application for memory leaks, you will find other errors. When we check the application for memory leaks, we want to find all the objects that exist, but should not exist. Finding such objects, we immediately understand what extra operations continue to be performed.

Secondly , not all applications consume little memory, and not all phones allocate a lot of memory.

Remember the application that crashed after only 5 minutes and 55 unloaded screens? So for the same application, I receive 1-2 weekly reports of a crash from OutOfMemoryException (mainly from devices up to 4.0; the application has 50,000 installations). And this despite the fact that there are no memory leaks in the application. Therefore, even now you can pretty much spoil your karma by laying out an application with memory leaks, especially if your application consumes a lot of memory. As usual in the android world, a harsh present separates us from a brilliant future.

Thirdly , a man must be able to do everything! (I promised that all 3 reasons would be serious)

Now that I hope I convinced you to catch memory leaks, let's look at the main reasons for their occurrence.

Never save activity references (view, fragment, service) in static variables


One of the first questions that every novice developer faces is how to transfer an object from one activity to the next. The simplest and most incorrect solution that I periodically see is writing the first activity to a static variable and accessing this variable from the second activity. This is an extremely unsuccessful approach. Not only because it instantly leads to a memory leak (a static variable will continue to exist as long as the application exists, and the activity it refers to will never be unloaded). This approach can also lead to a situation where you will exchange information with the wrong screen, because a screen that is invisible to the user can at any time be destroyed and recreated only when the user returns to it.

Why is activity leakage such a big problem? The fact is that if the garbage collector does not collect activity, then it will not collect all view and fragment, and with them all other objects located on activity. Including pictures will not be released. Therefore, the leak of any activity is, as a rule, the biggest memory leak that may be in your application.

Never write activity references to static variables. Use the transmission of objects through Intent , or in general, transfer not the object, but the id of the object (if you have a database from which this id can then be retrieved).

This item also applies to any objects whose lifetime is controlled directly or indirectly by android. Those. to view, fragment, service, etc ..

View and fragment objects contain a link to the activity in which they are located, therefore, if one single view leaks, everything - activity and all view in it leaks immediately, and, with them, everything is drawable and everything that any element has There is a link from the screen!

Be careful when transferring links to activity (view, fragment, service) to other objects


Consider a simple example: your application for a social network displays the last name, first name and rating of the current user on each screen of the application. An object with the profile of the current user exists from the moment you log into your account until you log out of it, and all screens of your application turn to the same object for information. This object also periodically updates data from the server, as the rating can change frequently. It is necessary that an object with a profile notify the current activity of a rating update. How to achieve this? Very simple:

@Override
protected void onResume() {
	super.onResume();
	currentUser.addOnUserUpdateListener(this);
}

How to achieve memory leak in this situation? Also very easy! Just forget to unsubscribe from notifications in the onPause method:

@Override
protected void onPause() {
	super.onPause();
	/* Забудьте про следующую строчку и вы получите серьёзную утечку памяти */
	currentUser.removeOnUserUpdateListener(this);
}

Due to such a memory leak, activity will continue to update the interface every time the profile is updated even after the screen is no longer visible to the user. Worse, in this way the screen can sign 2, 3 or more times for the same notification. This can lead to visible interface brakes at the time of updating the profile - and not only on this screen.

What to do to avoid this error?

Firstly , of course, you should always carefully monitor that you have unsubscribed from all notifications when the activity leaves the background.

Secondly , you should periodically check your application for memory leaks.

Thirdly , there is an alternative approach to the problem: you can save not links to objects, butweak links . This is especially useful for view class heirs - they don’t have an onPause method and it’s not clear at what point they should unsubscribe from a notification. Weak links are not considered garbage collectors as links between objects, so an object to which only weak links exist will be destroyed, and the link will stop referencing the object and will be null. In order not to mess around with weak links that are not very convenient to use, you can use the following template class approximately:

public class Observer {
	private ArrayList strongListeners = new ArrayList();
	private ArrayList> weakListeners = new ArrayList>();
	public void addStrongListener(I listener) {
		strongListeners.add(listener);
	}
	public void addWeakListener(I listener) {
		weakListeners.add(new WeakReference(listener));
	}
	public void removeListener(I listener) {
		strongListeners.remove(listener);
		for (int i = 0; i < weakListeners.size(); ++i) {
			WeakReference ref = weakListeners.get(i);
			if (ref.get() == null || ref.get() == listener) {
				weakListeners.remove(i--);
			}
		}
	}
	public List getListeners() {
		ArrayList activeListeners = new ArrayList();
		activeListeners.addAll(strongListeners);
		for (int i = 0; i < weakListeners.size(); ++i) {
			WeakReference ref = weakListeners.get(i);
			I listener = ref.get();
			if (listener == null) {
				weakListeners.remove(i--);
				continue;
			}
			activeListeners.add(listener);
		}
		return activeListeners;
	}
}

Which will work something like this:

public class User {
	...
	public interface OnUserUpdateListener {
		public void onUserUpdate();
	}
	private Observer updateObserver = new Observer();
	public Observer getUpdateObserver() {
		return updateObserver;
	}
}
...
@Override
protected void onFinishInflate() {
	super.onFinishInflate();
	/* Мы подписываемся на уведомления при создании объекта */
	currentUser.getUpdateObserver().addWeakListener(this);
}
/* ... и никогда от этих уведомлений не отписываемся */
...

Yes, you can get unnecessary updates to this view. But often this is the lesser of evils. And, in any situation, you will not receive a memory leak.

There is only one subtlety when using the addWeakListener method: someone must reference the object you add. Otherwise, the garbage collector will destroy this object before it receives its first notification:

/* Не делайте так! */
currentUser.getUpdateObserver().addWeakListener(new OnUserUpdateListener() {
	@Override
	public void onUserUpdate() {
		/* Этот код не будет вызван */
	}
});

Timers and threads that are not canceled when exiting the screen


I already talked about this problem above: so, you have developed an application for a social network. In this application, you can exchange messages between users, and you add a timer to the messaging screen that makes a request to the server every 10 seconds in order to receive new messages, but you forgot to turn off this timer when you exit the screen:

public class HandlerActivity extends Activity {
	private Handler mainLoopHandler = new Handler(Looper.getMainLooper());
	private Runnable queryServerRunnable = new Runnable() {
		@Override
		public void run() {
			new QueryServerTask().execute();
			mainLoopHandler.postDelayed(queryServerRunnable, 10000);
		}
	};
	@Override
	protected void onResume() {
		super.onResume();
		mainLoopHandler.post(queryServerRunnable);
	}
	@Override
	protected void onPause() {
		super.onPause();
		/* Вы забыли написать строчку ниже и в вашем приложении появилась утечка памяти */
		/* mainLoopHandler.removeCallbacks(queryServerRunnable); */
	}
	...
}

Unfortunately, this problem is difficult to avoid. The only two tips you can give will be the same as in the previous paragraph: be careful and periodically check the application for memory leaks. You can also use the approach similar to the previous paragraph using weak links .

Never save references to fragment in activity or another fragment


I have seen this error many times. Activity stores links to 5-6 running fragments even though only 1 is always visible on the screen. One fragment stores a link to another fragment. Fragments visible on the screen at different times communicate with each other via direct cached links. In such applications, the FragmentManager most often plays a rudimentary role - at the right moment it replaces the contents of the container with the desired fragment, and the fragments themselves are not added to the back stack (adding the fragment to which you have a direct link to the back stack will sooner or later lead to that the fragment will be unloaded from memory; after returning to this fragment, a new one will be created, and your link will continue to refer to the existing, but invisible to the user fragment).

This is a very bad approach for a number of reasons.

Firstly, if you store direct links to 5-6 fragments in activity, then this is the same as if you stored links to 5-6 activity. The entire interface, all pictures and all logic of 5 unused fragments cannot be unloaded from memory while activity is running.

Secondly, these fragments become extremely difficult to reuse. Try to transfer the fragment to another place of the program, provided that it must be run in the same activity with fragments, x, y and z, which you do not need to transfer.

Treat fragments as activity. Make them as modular as possible, communicate between fragments only through activity and fragmentManager. This may seem like an overly complex system: why try so hard when you can just pass the link? But, in fact, this approach will make your program better and easier.

There is an excellent official article from Google on this topic: “Communicating with Other Fragments” . Reread this article and never again save pointers to snippets.

General rule


After reading the four previous paragraphs, you may have noticed that they are practically no different. All these are special cases of one general rule.

All memory leaks occur if and only if you save a reference to an object with a short life cycle (short-lived object) in an object with a long life cycle (long-lived object).

Keep this in mind and always be mindful of such situations.

This rule does not have a beautiful short name such as KISS, YAGNI or RTFM, but it applies to all languages ​​with a garbage collector and to all objects, and not just to activity in android.

Now that I, hopefully, have shown the main sources of memory leaks, let's finally move on to identifying them in a working application.

Where to click to find out which line in the application to correct?


So, you know how to avoid memory leaks, but this does not protect you from typos, bugs, and projects that you wrote before you learned how to avoid memory leaks.

In order to determine the presence and source of memory leaks in the application, you need a little time and MAT . If you have never used MAT before, install it as a plugin to eclipse , open DDMS perspective and find the “Dump HPROF file” button. Pressing this button will open the memory dump of the selected application. If you use Android Studio, the process will be a little more complicated, since at the moment MAT still does not exist as a plug-in for Android Studio. Put the MAT as a separate program and use the instructions with stackoverflow .

Follow these steps:

  1. Install the application on a device connected to the computer and use it in such a way that it appears on every screen at least once. If one screen can be opened with different parameters, try to open it with all possible combinations of parameters. In general - go through the entire application, as if you were checking it before release. After you have completed all the screens, press the back button until you exit the application. Do not press the home button - your task is to terminate all running activities, and not just hide them.
  2. Press the Cause GC button several times. If you do not, the dump will show objects that are subject to destruction by the garbage collector, but have not yet been destroyed.
  3. Dump the application memory by clicking on the "Dump HPROF file" button.
  4. In the window that opens, make an OQL query: "SELECT * FROM instanceof android.app.Activity"


    The list of results should be empty. If there is at least one element in the list, then this element is your memory leak. In the screenshot you see just such an element - HandlerActivity: this is a memory leak. Follow steps 8-10 for each item in the list.
  5. Perform similar queries for Fragment descendants: “SELECT * FROM instanceof android.app.Fragment”. As in the previous case, all that fell into the list of results is memory leaks. Follow paragraphs 8-10 for each of them.
  6. Open the histogram. The results displayed in the histogram differ from the results displayed in the OQL in that the histogram displays classes, not objects. In the filter field, enter the package name used for your classes (in the screenshot it is com.examples.typicalleaks) and sort the results by the objects column (how many objects of this class currently exist in the system). Please note that the results also include classes whose 0 instances existed at the time of the dump. These classes do not interest us. If there really are a lot of objects, select the entire table, right-click and select Calculate Precise Retained Size. Sort the table by the Retained Heap field and consider only objects with large Retained Heap values, for example, more than 10000.


    This time, not all class objects that you see in the list are memory leaks. However, all these classes are the classes of your application, and you should roughly understand how many objects of each of these classes should exist at the moment. For example, in the screenshot we see 6 objects of the Example class and one array Example []. This is normal - the Example class is enum, its objects were created at the first call and will exist as long as the application exists. But HandlerActivity and HandlerActivity $ 1 (the first anonymous class declared inside the HandlerActivity.java file) are memory leaks already familiar to us. Right-click on a suspicious class, select list objects, perform steps 8-10 for one of the objects from the resulting list.
  7. If by this step you have not accumulated a single suspicious object - congratulations! There are no significant memory leaks in your application.
  8. Right-click on the suspicious object and select Merge Shortest Paths to GC Roots - exclude all phantom / weak / soft etc. references.
  9. Open the tree. You should get something like the following:


    At the very bottom you should see your suspicious object. At the very top is the root of the garbage collector. All in the middle is the objects connecting your suspicious object to the root of the garbage collector. It is this chain that does not allow the garbage collector to destroy a suspicious object. This chain should be read as follows: the variable of the object above the list is written in bold, which contains a link to the object to the right of the variable name. Those. in the screenshot, we see that the mMessages variable of the MessageQueue object contains a reference to the Message object, which contains the callback variable that refers to the HandlerActivity $ 1 object, which contains a reference to the HandlerActivity object in the this $ 0 variable. In other words, our suspicious HandlerActivity object holds the first Runnable declared in the HandlerActivity.java file, since it is added to the Handler using the post or postDelayed method. Find the last class from the bottom of the list that is part of your application, right-click on it and select Open Source File.
  10. Correct the application code in such a way as to break the chain between the suspicious object and the root of the garbage collector at the moment when the suspicious object is no longer needed. In our example, we just need to call the Handler.removeCallbacks (Runnable r) method in the onPause HandlerActivity method.
  11. After you have dealt with all the suspicious objects, repeat the algorithm from step 1 to verify that everything is working fine now.


Conclusion


If you clicked all the screens in your application and did not find any suspicious objects, then with a probability of 99.9%, there are no serious memory leaks in your application.

These checks are really enough for almost any application. You should only be interested in memory leaks that really can affect the application. Leaking an object containing a string uuid and a couple of short lines is a mistake that you just shouldn't waste your time fixing.


Also popular now: