Утечка памяти android studio

Утечка памяти android studio

Что такое Утечки памяти (Memory leaks) в Андроид разработке ?

Приложение создает объекты, они лежат в памяти и не могут оттуда отчиститься после завершения своей работы.

Почему так происходит ?

Java имеет собственные средства очистки памяти от неиспользуемых элементов. Это garbage collection .

Сборщик мусора помечает все объекты, которые можно удалить, если на них нет ссылок. Memory leak — это как раз потерявшаяся ссылка, которая показывает, что объект удалить нельзя.

Сложность этого бага заключается в том, что до определенного времени его не видно и он может не мешать.
Есть хорошая цитата Бенджамина Франклина “Маленькая течь топит большой корабль."
Утечки памяти съедают оперативную память приложения. Количество неочищенной памяти будет расти и однажды может привести к тому, что ваше приложение начнет тормозить и крашиться . Что приведет к недовольству пользователей и, скорее всего, приложение будет удалено.

Одно из самых опасных , когда программе теряется ссылка на view . Казалось бы, что такого, view маленькая на экране. Однако, стоит помнить, что у view есть ссылка на Activity (Fragment). A если ссылка на view не удалилась, то и Activity(Frsgment) тоже жива. A у Activity(Fragment) есть ссылки на все view, что есть на экране.

Как понять, есть ли утечки памяти ?

Отыскать мемори лики можно разными способами. Но самый простой — воспользоваться профайлером Android studio.

Порядок действий:

  • Запускаете проект
  • В нижней панели нажимаете "Profiler"
  • Выбираете "Memory"
  • Нажимаете "Force Garbage Collection" (затем нужно немного подождать)
  • Нажимаете "Dump Java heap"
  • Фильтруете список по необходимым классам
  • Смотрите количество объектов

На картинке выше видно, что имеется четыре объекта Activity . Это произошло, потому что программист позволил программе потерять ссылки на view . А пользователи очень любят поворачивать экран телефона. С каждой сменой ориентации активити пересоздавалась, а старая оставалась валяться в глубинах памяти.

Если вы хотите способ отлова утечек памяти поинтереснее, тогда предлагаю вам ознакомиться с библиотекой LeakCanary .

Как избежать утечек памяти ?

Вот несколько советов:

1. Не создавать статические ссылки на view. У статических полей жизненный цикл такой же, как у вашего приложения.

2. Не передавать ссылки в сущности, которые живут дольше, чем объект, который вы передали. Например: не стоит передавать в класс Runnable ссылки на View , так как новый поток продолжить жить даже после пересоздания активити.

А как передавать view в Runnable ?

Использовать другие типы ссылок.
В Java, кроме обычных "жестких ссылок" существуют другие "мягкие ссылки" и "слабые" . Правильно их называть:

  • WeakReference
  • SoftReference
  • Phantom Reference

Наличие "мягких ссылок" уже не будет мешать garbage collection удалять Acrivity. Подробнее почитать о них вы можете по ссылке: отличия между слабыми, мягкими, фантомными и обычными ссылками в Java

Без утечек памяти код выше выглядел бы так:

3. Нужно делать внутренние классы Activity статическими. У внутреннего класса активити про обращении к view (и любым другим объектам) создаются ссылки на этот объект. И если внутренний класс живет дольше, чем активити, то появляется Memory leak.

В данной статье была рассказана лишь небольшая часть информации о том, что же такое утечки памяти в Андроид разработке и самые простые способы их избежать. Существует еще множество сложных ситуаций. Некоторые их них будут разобраны в следующих статьях. Подписывайтесь на новые статьи.

Этой статьей мы открываем цикл статей на Хабре о нашей разработке под Android.
Согласно докладу компании Crittercism от 2012 года, OutOfMemoryError — вторая по распространенности причина «крашей» мобильных приложений.
Честно говоря, и в Badoo эта ошибка была в топе всех крашей (что неудивительно при том объеме фотографий, которые просматривают наши пользователи). Борьба с OutOfMemory — занятие кропотливое. Мы взяли в руки Allocation Tracker и начали играться с приложением. Наблюдая за данными зарезервированной памяти, мы выявили несколько сценариев, при которых выделение памяти росло с подозрительной стремительностью, забывая при этом уменьшаться. Вооружившись несколькими дампами памяти после этих сценариев, мы проанализировали их в MAT (http://www.eclipse.org/mat/).
Результат был занимательный и позволил нам в течение нескольких недель снизить количество крашей в разы. Что-то было специфично для нашего кода, но также выявились типичные проблемы, присущие большинству Android приложений.
Сегодня поговорим о конкретном случае утечки памяти. О нем многие знают, но часто закрывают на это глаза (а зря).

Речь пойдет об утечках памяти, связанных с неправильным использованием android.os.Handler. Не совсем очевидно, но все, что вы помещаете в Handler, находится в памяти и не может быть очищено сборщиком мусора в течении некоторого времени. Иногда довольно длительного.
Чуть позже мы покажем на примерах, что происходит и почему память не может быть освобождена. Если вы не любопытный, но хотите знать, как бороться с проблемой, то перейдите к выводам в конце статьи. Или сразу отправляйтесь на страничку маленькой библиотеки, которую мы выложили в открытый доступ: https://github.com/badoo/android-weak-handler.

Итак, что же там «течет»? Давайте разберемся.

Простой пример

Это очень простой класс Activity. Предположим, что нам нужно поменять текст по прошествии 800 секунд. Пример, конечно, нелепый, но зато хорошо продемонстрирует нам, как текут ручьи нашей памяти.
Обратите внимание на анонимный Runnable, который мы постим в Handler. Так же важно обратить внимание на длительный тайм-аут.
Для теста мы запустили этот пример и повернули телефон 7 раз, тем самым вызвав смену ориентации экрана и пересоздание Activity. Затем сняли дамп памяти и открыли его в MAT (http://www.eclipse.org/mat/).

С помощью OQL запускаем простой запрос, который выводит все инстансы класса Activity:

В памяти висит 7 инстансов Activity. Это в 7 раз больше, чем нужно. Давайте разберемся, почему сборщик мусора не смог удалить отработавшие объекты из памяти. Откроем кратчайший граф ссылок на один из Activity:

На скриншоте видно, что на Activity ссылается this$0. Это неявная ссылка из анонимного класса на внешний класс. В Java любой анонимный класс всегда имеет неявную ссылку на внешний класс, даже если вы никогда не обращаетесь к внешним полям или методам. Java не идеальна, а жизнь — это боль. Такие дела, котаны.

Далее, ссылка на this$0 хранится в callback, который хранится в связанном списке сообщений. В конце цепочки — локальная ссылка в стеке главного потока. По всей видимости, это локальная переменная в главном цикле UI потока, которая освободится, когда цикл отработает. В нашем случае это произойдет после того, как приложение завершит свою работу.

Читайте также:  Коньяк фуссиньи селексьон отзывы

Итак, после того как мы поместили Runnable или Message в Handler, он будет хранится в списке сообщений в LooperThread до тех пор, пока сообщение не отработает. Вполне очевидно, что если мы поместим отложенное сообщение, то оно будет лежать в памяти до тех пор, пока не настанет его время. Вместе с сообщением в памяти будут лежать все объекты, на которые ссылается сообщение, явно и неявно.
И с этим нужно что-то делать.

Решение с использованием статического класса

Давайте попробуем решить нашу проблему, избавившись от ссылки this$0. Для этого переделаем анонимный класс в статический:

Запускаем, пару раз поворачиваем телефон и собираем дамп памяти.

Снова больше одной Activity? Давайте посмотрим, почему сборщик мусора не смог их удалить.

Обратите внимание на самый низ графа ссылок: Activity сохранен в ссылке mContext из mTextView внутри класса DoneRunnable. Очевидно, что использование статического класса самого по себе недостаточно, чтобы избежать утечки памяти. Нам нужно сделать кое-что еще.

Решение с использованием статического класса и WeakReference

Продолжим последовательный метод избавления от ссылки на TextView, которую мы нашли в ходе изучения дампов памяти.

Обратите внимание, что мы сохраняем ссылку на TextView в WeakReference. Использование WeakReference требует особой аккуратности: такая ссылка в любой момент может обнулиться. Поэтому сначала сохраняем ссылку в локальную переменную и работаем только с последней, проверив ее на null.

Запускаем, поворачиваем и собираем дамп памяти.

Мы добились желаемого! Только один Activity в памяти. Проблема решена.

Для использования данного подхода нам необходимо:

  • использовать статический внутренний или внешний класс;
  • использовать WeakReference для всех объектов, на которые мы ссылаемся.

Хорош ли данный метод?
Если сравнивать оригинальный код и «безопасный» код, то в глаза бросается большое количество «шума». Он отвлекает от понимания кода и усложняет его поддержку. Написание такого кода — то еще удовольствие, не говоря уж о том, что можно что-то забыть или забить.

Хорошо, что есть решения получше.

Очистка всех сообщений в onDestroy

У класса Handler есть занимательный и очень полезный метод — removeCallbacksAndMessages, который принимает null в качестве аргумента. Он удаляет все сообщения, находящиеся в очереди данного Handler’а. Давайте используем его в onDestroy.

Запустим, повернем и снимем дамп памяти.

Прекрасно! Только один класс Activity.

Этот метод намного лучше предыдущего: количество сопутствующего кода минимально, риски допустить ошибку намного ниже. Одна беда — не забыть бы вызвать очистку в методах onDestroy или там, где вам нужно почистить память.

У нас в запасе есть еще один метод, который, возможно, понравится вам намного больше.

Решение с использованием WeakHandler

Команда Badoo написала свой Handler — WeakHandler. Это класс, который ведет себя совершенно как Handler, но исключает утечки памяти.

Он использует мягкие и жесткие ссылки для избежания утечек памяти. Принцип его работы мы опишем немного позже, а пока давайте взглянем на код:

Очень похоже на оригинальный код, не так ли? Лишь одна маленькая деталь: вместо использования android.os.Handler мы использовали WeakHandler. Давайте запустим, повернем телефон несколько раз и снимем дамп памяти.

Наконец-то! Код чист как слеза и память не течет.

Если вам понравился этот метод, то вот хорошая новость: использовать WeakHandler очень просто.

Добавьте maven-зависимость в ваш проект:

Импортируйте WeakHandler в вашем коде:

Принцип работы WeakHandler

Главная идея — держать жесткую ссылку на сообщения или Runnable до тех пор, пока существует жесткая ссылка на WeakHandler. Как только WeakHandler может быть удален из памяти, все остальное должно быть удалено вместе с ним.

Для простоты объяснения мы покажем простенькую диаграмму, демонстрирующую разницу между помещением анонимного Runnable в простой Handler и в WeakHandler:

Обратите внимание на верхнюю диаграмму: Activity ссылается на Handler, который постит Runnable (помещает его в очередь сообщений, на которые ссылается Thread). Все неплохо, за исключением неявной обратной ссылки из Runnable на Activity. Пока Message лежит в очереди, которая живет, пока жив Thread, весь граф не может быть собран сборщиком мусора. В том числе и толстая Activity.

В нижней диаграмме Activity ссылается на WeakHandler, который держит Handler внутри. Когда мы просим его поместить Runnable, он заворачивает его в WeakRunnable и постит в очередь. Таким образом, очередь сообщений ссылается только на WeakRunnable. WeakRunnable содержит WeakReference на изначальный Runnable, т.е. сборщик мусора может его очистить в любой момент. Что бы он его не очистил раньше времени, WeakHandler держит жесткую ссылку на Runnable. Но как только сам WeakHandler может быть удален, Runnable так же может быть удален.

Нужно быть аккуратным и не забывать, что на WeakHandler должна быть ссылка извне, иначе все сообщения будут очищены вместе с ним сборщиком мусора.

Выводы

Использование postDelayed в Android не так просто, как кажется: нужно совершать дополнительные действия, чтобы память не текла. Для этого можно применять следующие методы:

  • использовать статический внутренний класс Runnable/Handler с WeakReferences на внешний класс;
  • чистить все сообщения в классе Handler из метода onDestroy;
  • использовать WeakHandler от Badoo (https://github.com/badoo/android-weak-handler).

Выбор за вами. Первый метод точно не для ленивых. Второй метод выглядит довольно простым, но требует дополнительной работы. Третий же — наш фаворит, но нужно быть внимательным: на WeakHandler должна быть внешняя ссылка до тех пор, пока он вам нужен, иначе сборщик мусора его удалит вместе со всеми сообщениями из очереди.

Удачной вам борьбы! Оставайтесь с нами — у этой темы будет продолжение.

Every app needs memory as a resource to do its work. To make sure each app in Android has enough memory, Android system needs to manage memory allocation efficiently. Android runtime triggers Garbage Collection (GC) when memory runs short. The purpose of GC is to reclaim memory by cleaning up objects that are no longer useful. It achieves it in three steps.

  1. Traverses all object references in memory from GC roots and marks active objects which has references from GC roots.
  2. All objects which are not marked (garbages) are wiped from memory.
  3. Rearrange live objects
Читайте также:  Греется ноутбук при играх что делать

In short, everything serving the user should be kept in memory and everything else are wiped out from memory to free up resources.

However, when code are written in a bad manner that unused objects are referenced somehow from reachable objects, GC would mark unused objects as useful object and therefore would not be able to remove them. This is called a memory leak.

No objects should stay in memory longer than they should. They occupy valuable resources which could otherwise be used for things that provides real value to the user. For Android specifically, it causes the following problems.

1. Less usable memory would be available when memory leak happens. As a result, Android system will trigger more frequent GC events. GC events are stop-the-world events. It means when GC happens, the rendering of UI and processing of events will stop. Android has a 16ms drawing window. When GC takes long than that, Android starts loosing frames. Generally, 100 to 200ms is the threshold beyond which users will perceive slowness in an application [1].

In Android, application responsiveness is monitored by the Activity Manager and Window Manager system services. Android will display the ANR dialog for a particular application when it detects one of the following conditions [1]:

  • No response to an input event (such as key press or screen touch events) within 5 seconds.
  • A BroadcastReceiver hasn’t finished executing within 10 seconds.

I am sure no users would love to see this app not responding popup.

2. When your app has memory leaks, it cannot claim memory from unused objects. As a result, it will ask Android system for more memory. But there is a limit. The system will eventually refuse to allocate more memory for your app. When this happens, app user will get an out-of-memory crash. Of course no one likes crashes. Users might uninstall your app or start giving your app bad reviews.

3. Memory leak issues are mostly hard to find in QA/testing. They are hard to reproduce. And the crash report is usually hard for reasoning because it can happen any time, anywhere when memory allocation is refused by Android system.

Finding leaks requires good understanding on how GC works. It requires diligence in writing code and code review. But in Android, there are some good tools which can help you identify possible leaks or make sure if there is a leak when some piece of code seems suspicious.

1. Leak Canary from Square is a good tool for detecting memory leaks in your app. It creates weak references to activities in your app. (You can also customize it by adding watches to any other objects.) It then checks if the reference is cleared after GC. If not, it dumps heap into a .hprof file and analyze it to confirm if there is a leak. If there is one, it shows a notification and in a separate app, it shows the reference tree of how the leak happens. You can find more about Leak Canary in this article: LeakCanary: Detect all memory leaks. I highly recommend that you install Leak Canary to your developer/testing build. It helps developers and QA to find memory leaks before your app reaches the hands of your users.

2. Android Studio has a handy tool for detecting memory leaks. If you suspect a piece of code in you app might leaks an Activity, you can do this.

Step 1: Compile and run the debug build on a device or emulator connecting to you computer.

Step 2: Go to the suspicious activity, then go back to previous activity which will pop the suspicious activity from the task stack.

Step 3: In Android Studio -> Android Monitor window -> Memory section, click on Initiate GC button. Then click on Dump Java Heap button.

Step 4: When Dump Java Heap button is pressed, Android Studio will open the dumped .hprof file. In the hprof file viewer, there are a couple of ways you can check the memory leak. You can use the Analyzer Tasks tool on the top right conner to detect leaked activities automatically. Or you can switch the view mode to Package Tree View from top left conner, find the activity which should be destroyed. Check the Total Count of the activity object. If there are 1 or more instances, it means there is a leak.

Step 5: Once you find the leaked Activity, check the reference tree on the bottom and find out what object is referencing the should-have-been-dead activity.

You can find more information about the Android Studio feature from HPROF Viewer and Analyzer.

There are lots of ways you can cause a memory leak in Android. To summarize, there are mainly three categories.

  1. Leak activity to a static reference
  2. Leak activity to a worker thread
  3. Leak thread itself

In my Github repo SinsOfMemoryLeaks , I made an app which just leak memories in various ways.

frank-tan/SinsOfMemoryLeaks

SinsOfMemoryLeaks — Some common patterns of memory leaks in Android development and how to fix/avoid them

github.com

In Leak branch, you can see all the code with various memory leaks. You can also run it on a device or emulator and use the fore-mentioned tools to track the leaks. In FIXED branch, you will see the how the leaks are fixed. If you are not convinced, you can again use the fore-mentioned tools to see if the leaks are really fixed. The two branches have different app ids, so you can install them on the same device and play with them side by side.

Now I will quickly go through leaks of different flavors in the 3 main categories.

Leak activity to a static reference

A static reference lives as long as your app is in memory. An activity has lifecycles which are usually destroyed and re-created multiple times during you app’s lifecycle. If you reference an activity directly or indirectly from a static reference, the activity would not be garbage collected after it is destroyed. An activity can range from a few kilo bytes to many mega bytes depending on what contents are in it. If it has a large view hierarchy or high resolution images, it can make a large chunk of memory leaked.

Читайте также:  Как подключить блютуз модуль к усилителю

A few flavors of leak in this category can be

Leak activity to a worker thread

A worker thread can also out-live an Activity. If you reference an Activity directly or indirectly from a worker thread which lives longer than the Activity, you also leak the Activity object. A few flavor of this category can be

The same principle applies to other threading techniques such as a thread pool or ExecutorService .

Leak the thread itself

Every time you start a worker thread from an activity, you are responsible of managing the worker thread yourself. Because the worker thread can live longer than the Activity, you should stop the worker thread properly when the Activity is destroyed. If you forget to do so, you are risking leaking the worker thread itself. Example is here https://github.com/frank-tan/SinsOfMemoryLeaks/blob/LEAK/app/src/main/java/com/franktan/memoryleakexamples/vialongrunningtask/LeakThreadsActivity.java

Ideally you should avoid writing any code which causes memory leaks in the first place, and fix all memory leaks existing in your app. But in reality, if you are dealing with an old code base and need to prioritize tasks including fixing memory leaks, you can evaluate the severity in the following aspects.

1. How big is the leaked memory?

Not all memory leaks are equal. Some leaks a few kilo bytes; some may leak many mega bytes. You can find it out by using the fore-mentioned tools and decide if the size of memory leaked is critical for the devices of your user base.

2. How long does leaked object reside in memory for?

Some leaks through work thread lives as long as the worker thread itself. You should examine how long in the worst scenario your worker thread lives. In my code examples, I have endless loops in the worker thread, so it hold on the leaked object in the memory forever. But in reality, most worker thread does simple tasks such as accessing file system or doing network calls, which is either short lived or you would usually set a timeout anyway. This max time for the leaking is a consideration in determining the priority of fixing memory leaks.

3. How many objects can it leak?

Some memory leak leaks only one object, such as the ones in the static references example in my repo. As soon as the new activity is created, the reference start referencing the new activity. The leaked old activity is clear to be garbage collected. So the max leak is always the size of one activity instance. Other leaks, however, keep leaking new objects as they are created. In the Leaking Threads example, the activity leak one thread every time it is created. So if you rotate the device 20 times, 20 worker threads are leaked. This can be really bad as the app will soon chow up all available memory on the device if it keeps leaking new instances. I would most likely fix all this type of leaks even if one object instance is relatively small.

Check out the FIXED branch https://github.com/frank-tan/SinsOfMemoryLeaks/tree/FIXED of my repo. The key takeaways are

  1. Be very careful when you decide to have a static variable in your activity class. Is it really necessary? Is it possible that the static variable references activity directly or indirectly (indirectly can be referencing inner class object, an attached view, etc.)? If so, do you clear the reference on Activity onDestroy?
  2. When you pass your activity as a listener to a singleton object or a x-manager instance, make sure you understand what the other object does with the activity instance you passed in. Clear the reference (set the listener to null) if necessary on Activity onDestroy.
  3. When you create an inner class in your activity class, make it static if possible. Inner classes and anonymous classes have a implicit reference to the containing class. So if the instance of the inner/anonymous class lives longer than the containing class, you are in trouble. For example, if you create an anonymous runnable class and pass it to a worker thread, or an anonymous handler class and use it to pass tasks to a different thread, you are risking leaking the containing class object. To avoid the leaking risk, use static class as opposed to inner/anonymous class.
  4. If you are writing a singleton or a x-manager class, you need to store reference of a listener instance, and you don’t have control of how the user of your class manage the reference, you can use WeakReference for the listener reference. WeakReference does not prevent their referents from being cleared from GC and reclaimed [4]. Although this feature sounds great in preventing memory leaks, it may also be a side effect, because there is no guarantee the referenced object is live when they are needed. So use it as the last resort in fixing memory leaks.
  5. Always terminate worker threads you initiated on Activity onDestroy().

We studied what a memory leak is, how it happens, what consequence it causes in Android system. Then we introduced two tools of detecting and identifying memory leaks, examined common memory leak patterns in Android, how to evaluate the severity of a leak and how to avoid/fix the common leaks. Don’t forget to check out the code examples for the common memory leak patterns and fixes from my Github repo. Happy making Android apps, everyone 🙂

Ссылка на основную публикацию
Удалить одноклассники страницу с телефона айфон
Если вы хотите удалить свою страницу (профиль) в Одноклассниках, особенно если это требуется сделать со смартфона Android или iPhone —...
Тест автомобильных компрессоров за рулем
Жужжат много, а толку мало. Среди 12 образцов доступных (не дороже 2000 рублей) автомобильных компрессоров треть оказалась «неправильной». Стоит ли...
Тест железа в играх
Как найти игры для моего компьютера? На данной странице сервис выдаст полный список игр которые подходят вам исходя из параметров...
Удалить папку не удалось найти этот элемент
В этой инструкции подробно о том, как удалить файл или папку, если при попытке это сделать в Windows 10, 8...
Adblock detector