Android studio memory indicator

Native Memory Profiling with Android Studio 4.1

This is second in a two part series on What’s New in Profilers in Android Studio 4.1. Our previous post focused on What’s New in System Trace .

We’ve heard from those of you using C++ that debugging native memory can be fairly difficult, particularly in games. With Android Studio 4.1, we’ve implemented the ability to record call stacks of native memory allocations in our Memory Profiler. The native memory recording is built on top of the Perfetto backend, the next generation performance instrumentation and tracing solution for Android.

A common technique when trying to debug memory issues is to understand what is allocating memory and what is freeing memory. The rest of this article will walk you through how to use the Native Memory Profiler to help track down a leak, using the Gpu Emulation Stress Test as an example project.

Getting Started

When a memory leak is suspected, it’s often a good idea to start at a high level and watch for patterns in the system memory. To do this click the profile button in Android Studio, and enter the memory profiler for more detailed memory tracking information.

After running the simulation a few times we can see a few interesting patterns.

  1. The GPU memory increases as one may expect from a GPU emulation app, however it also looks like this memory gets properly cleaned up after the Activity is finished.
  2. The Native memory grows each time we enter the GpuEmulationStressTestActivity, however this memory does not seem to reset after each run, which might be indicative of a leak.

Native Memory Table View

Starting with Android Studio 4.1 Canary 6, we can grab a recording of native memory allocations to analyze why memory isn’t being released. To do this with the GPU emulation app, I stopped the running app and started profiling a fresh instance. Starting from a clean state, especially when looking at an unfamiliar codebase, can help narrow our focus. From the memory profiler I captured a native allocation recording throughout the duration of the GPU emulation demo. To do this restart the app by selecting Run-> Profile ‘app’. After the application starts and the profile window opens, click on the memory profiler and select “record native allocation”

The table view is useful for games/applications that use libraries implementing their own allocators highlighting malloc calls that are made outside of new.

When a recording is loaded, the data is first presented in a table. The table shows the leaf functions calling malloc. In addition to the function name, the table shows module, count, size, and delta. This information is sampled so it is possible not all malloc / free calls will be captured. This largely depends on the sampling rate, which will be discussed a bit later.

It is also useful to know where these functions that allocate memory are being called from. There are two ways to visualize this information. The first is by changing the “Arrange by allocation method” dropdown to “Arrange by call stack”. The table shows a tree of callstacks, similar to what you may expect from a CPU recording. If the current project has symbols (which is usually the case for debuggable builds; if you’re profiling an external APK check out the guide here) they will automatically be picked up and used. This allows you to right click on a function and “Jump to source”.

Memory Visualization (Native and non-native)

We’ve also added a new flame chart visualization to the memory profilers, allowing you to quickly see what callstacks are responsible for allocating the most memory. This is especially useful when a call stack is really deep.

There are four ways you can sort this data along the X axis:

  • “Allocation Size” is the default, showing the total amount of memory tracked.
  • “Allocation Count” shows the total number of objects allocated.
  • “Total Remaining Size” is the size of memory sampled throughout the capture that was not freed before the end of the capture.
  • “Total Remaining Count”, like the remaining size, is the count of objects captured but not freed before the end of the capture.

From here we can right click on the call stacks and select “Jump to Source” to take us to the line of code responsible for the allocation. However, taking a second glance at the visualization, we notice that the common parent, WorldState, is responsible for multiple leaks. To validate this, it can help to filter the results.

Filtering / Navigation

Like with the table view, the chart can be filtered using the filter bar. When the filter is used, the data in the chart is automatically updated to show only call stacks that have functions matching the word/regex searched.

70MB of our total assumed leak

Sometimes call stacks can get fairly long, or there just isn’t enough room to display the function name on screen. To assist with this, ctrl + mouse wheel will zoom in/out, or you can click on the chart to use W,A,S,D to navigate.

Verifying the findings

Adding a breakpoint and running the Emulation twice quickly reveals that on the second run we cause the leak by overwrite the pointer from our first run.

Читайте также:  Skachat igri dlya samsung android

As a quick fix to the sample we can delete the world after it is marked done, profiling the application again to validate the fix.

Ending where we started by looking at the high level memory stats. Validating that deleting sWorld at the end of the simulation frees up the 70mb held by our first run.

Startup profiling and sample rate setting.

The sample above shows how native memory tracking can be used to find and fix memory leaks. Another common use for native memory tracking is understanding where memory is going during startup of the application. In Android Studio 4.1, we also added the ability to capture native memory recordings from the startup of the application. This is available in the “Run/Debug Configurations” dialog under the “Profiling” tab.

You can customize the sampling interval or record memory at startup in the Run configuration dialog.

Here you can also change the sampling rate for new captures. A smaller sampling rate can have a large impact on overall performance, while a larger sampling rate can miss some allocations. Different sampling rates work for different types of memory problems.

Wrapping up

With the new native memory profiler finding memory leaks and understanding where memory is being held on to just got a little bit easier. Give the native memory profiler a try in Android Studio 4.1, and leave any feedback on our bug tracker. For additional tips and tricks be sure to also check out our talk earlier this year at the Google for Games summit, Android memory tools and best practices.

Источник

Improve your experience on Android Studio

Android Studio is our daily tool. It is essential to have the best experience while coding. So here is a list of simple customization tips for your favorite IDE.

1. Increase Android Studio memory

By default the RAM allocated to the IDE is 1280MB. If you’re an Android Developer and your machine allows it, it is strongly recommended to increase it. Especially if you happen to launch several projects simultaneously and thus several instances of Android Studio.

  • Click Help > Edit Custom VM Options to open your studio.vmoptions file.

Increase from 1280m to 4096m & increase MaxPermSize from 350m to 1024m:

  • Save your changes to the studio.vmoptions file, and restart Android Studio for your changes to take effect.

2. Show memory indicator on Android Studio

After increasing the RAM of your IDE, it can be interesting to display it to know exactly what is used in real time but also to be able to clear it in a simple click.

  • Click Preferences > Appearance & Behavior > Appearance

Источник

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

Когда мы пишем код, то создаём различные объекты, которые занимают память. Когда объект нам не нужен, то его нужно уничтожить, чтобы освободить память для других объектов. Этим занимается специальный сборщик мусора (garbage collector). Но иногда программа написана таким образом, что сборщик мусора думает, что объект вам ещё нужен и не удаляет его из памяти. Тем самым кусок памяти остаётся занятым. А если процесс создания новых объектов с неправильным поведением повторяется неоднократно, то память просто забивается. В конце концов приложение может израсходовать лимит выделяемой памяти. Это состояние и называют утечкой памяти, т.е. приложению было выделено определённое количество памяти, а на самом деле используется меньшее количество. Откат, распил бюджета, коррупция. В этом случае приложение перестаёт работать, зависает и падает с ошибкой.

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

Память условно имеет две области для хранения данных — стек (stack) и куча (heap).

Стек работает в порядке LIFO (Last In, First Out), то есть последний добавленный в стек фрагмент данных будет первым в очереди на вывод из стека. Каждый раз, когда функция объявляет новую переменную, переменная добавляется в стек, а когда эта переменная пропадает из области видимости, она автоматически удаляется из стека. Когда стековая переменная освобождается, эта область памяти становится доступной для других стековых переменных. Размер стека — это фиксированная величина, и превышение лимита выделенной на стеке памяти приведёт к переполнению стека. Размер задаётся при создании потока, и у каждой переменной есть максимальный размер, зависящий от типа данных.

Куча — это хранилище памяти, также расположенное в ОЗУ, которое допускает динамическое выделение памяти. Куча не имеет упорядоченного набора данных, это просто склад для ваших переменных. По завершении приложения все выделенные участки памяти освобождаются. Размер кучи задаётся при запуске приложения, но, в отличие от стека, он ограничен лишь физически, и это позволяет создавать динамические переменные.

Чтобы наглядно преддставить способ хранения объектов в памяти, напишем простую программу на Java.

Размещение в памяти при запуске выглядит следующим образом.

По рисунку видно, что в стек попали функция main(), переменная с примитивным типом int.

Также в стек попадает объект obj, когда он создаётся из класса Object, при этом в куче создаётся ссылка на класс (указатель).

Аналогично, в стеке появляется объект mem с ссылкой на класс в куче.

Для функции foo() в стеке создаётся отдельный блок. В этом блоке создаётся объект param с ссылкой в куче на класс Object и строковый объект с ссылкой в куче на отдельный блок String Pool.

Когда в программе выполнение доходит до закрывающей фигурной скобкой метода foo(), метод прекращает работу и объекты в стеке, относящиеся к блоку функции, освобождаются. Память выглядит следующим образом.

Последняя закрывающая фигурная скобка от функции main() закрывает эту функцию, освобождая свой блок данных.

Стек успешно очистился, когда все функции отработали. Но данные в куче ведут себя немного иначе. Они сами по себе не уходят. В Java имеется специальный помощник — сборщик мусора, который следит за порядком и если он заметит неиспользуемые объекты, то убирает их. Суть его работы состоит в том, чтобы смотреть, есть ли связь между данными в стеке и куче. Если у объекта нет ссылки на класс в куче, значит класс можно удалить из памяти. Идеальный порядок выглядит следующим образом.

Читайте также:  Android top down shooters

Существуют различные методики обнаружения утечек памяти и способы борьбы с ней. Вам нужно научиться устранять типовые утечки памяти.

Сначала ответим на вопрос: а зачем исправлять эти ошибки, чем это грозит? Даже с утечками памяти приложение может работать.

Можно провести эксперимент, намеренно создав утечку памяти — активность при каждом повороте будет создавать новый экземпляр. На современном устройстве таким образом можно создать несколько десятков новых экранов, прежде чем приложение закроется с ошибкой. Но в среднем, пользователь открывает 3-5 экранов, поэтому вероятность появление ошибки маловероятно. Но данный пример не должен успокаивать вас. Не все телефоны выделяют много памяти приложению.

Основные проблемные источники: Context и его производные (Activity), внутренние классы (Inner Classes), анонимные классы (Anonymous Classes), Handlers c Runnable, Threads, TimerTask, SensorManager и другие менеджеры.

Самая главная рекомендация — никогда не сохраняйте ссылки на Context, Activity, View, Fragment, Service в статических переменных.

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

Почему же утечка активности такая большая проблема? Дело в том, что если сборщик мусора не соберёт Activity, то он не соберёт и все View и Fragment, а вместе с ними и все прочие объекты, расположенные в Activity. В том числе не будут высвобождены картинки. Поэтому утечка любой активности — это, как правило, самая большая утечка памяти, которая может быть в вашем приложении.

Используйте передачу объектов через Intent, либо передавайте ID объекта (если у вас есть база данных, из которой этот id потом можно достать).

Этот пункт также относится к любым объектам, временем жизни которых напрямую или косвенно управляет Android, т.е. View, Fragment, Service и т.д.

Объекты View и Fragment содержат ссылку на Activity, в котором они расположены, поэтому, если утечёт один единственный View, утечёт сразу всё — Activity и все View в ней. И заодно все drawable и всё, на что у любого элемента из экрана есть ссылка!

Будьте аккуратны при передаче ссылки на Activity (View, Fragment, Service) в другие объекты.

Утечка через слушателей

Рассмотрим простой пример: ваше приложение для социальной сети отображает фамилию, имя и рейтинг текущего пользователя на каждом экране приложения. Объект с профилем текущего пользователя существует с момента входа в аккаунт до момента выхода из него, и все экраны вашего приложения обращаются за информацией к одному и тому же объекту. Этот объект также периодически обновляет данные с сервера, так как рейтинг может часто меняться. Необходимо, чтобы объект с профилем уведомлял текущую активность об обновлении рейтинга. Как этого добиться? Очень просто:

Как добиться в этой ситуации утечки памяти? Тоже очень несложно! Просто забудьте отписаться от уведомлений в методе onPause():

Из-за такой утечки памяти активность будет продолжать обновлять интерфейс каждый раз, когда профиль будет обновляться даже после того, как экран перестанет быть видим пользователю. Хуже того, таким образом экран может подписать 2, 3 или больше раза на одно и то же уведомление. Это может привести к видимым тормозам интерфейса в момент обновления профиля — и не только на этом экране.

Что делать, чтобы избежать этой ошибки? Во-первых, конечно нужно всегда внимательно следить за тем, что вы отписались от всех уведомлений в момент ухода Activity в фон.

Вы можете сохранять не ссылки на объекты, а слабые ссылки. Это особенно полезно для наследников класса View — ведь у них нет метода onPause() и не совсем понятно, в какой момент они должны отписываться от уведомления. Слабые ссылки не считаются сборщиком мусора как связи между объектами, поэтому объект, на который существуют только слабые ссылки, будет уничтожен, а ссылка перестанет ссылаться на объект и примет значение null.

Другой пример с использованием системных слушателей. Например, есть слушатель определения местоположения.

Если забудем снять регистрацию слушателя в onStop(), то пользователь может закрыть приложение, но сборщик мусора не сможет освободить память, так как LocationManager будет по-прежнему выполнять свою работу.

Пример утечки с внутренним классом

Часто в состав основного класса включают внутренний класс. Это нормально, но в некоторых случаях может стать причиной утечки памяти. Внутренний класс содержит ссылку на основной класс, но может иметь свой жизненный цикл.

Сам пример вполне нормальный. Но нужно помнить, что класс BackgroundTask хранит ссылку на активность. Если задача выполняется очень долго (плохое соединение, большая картинка на сервере), то сложная активность со всеми своими ресурсами остаётся в памяти, пока задача не будет отработана.

Есть разные варианты решения задачи. Часто рекомендуют подход с WeakReference.

Код усложнился, кроме того, вам придётся изучать устройство WeakReference.

Для примера на Kotlin можно убрать модификатор inner.

Если в качестве внутреннего класса использовать Handler, то студия будет выводить подсказку This Handler class should be static or leaks might occur (anonymous android.os.Handler). Код, чтобы увидеть подсказку.

Более подробное описание подсказки в студии:

Пример утечки с анонимным классом

Принцип утечки памяти схож с примером с внутренним классом, когда сохраняется ссылка на активность. Экземпляр анонимного класса живёт дольше, чем контейнер. Если анонимный класс вызывает какой-то метод, читает или записывает свойство в класс-контейнер, то он держит в памяти класс-контейнер.

Утечка через потоки

Случай первый — потоки. Создадим внутренний класс внутри активности. Внутренний класс будет иметь ссылку на активность.

Читайте также:  Приходит смс код для регистрации андроид что это

В обычной ситуации пользователь запустит активность, запустится задача на двадцать секунд.

Когда задача выполнится, стек освободит объекты.

Затем сборщик мусора освободит объекты в куче.

Когда пользователь закроет активность, основной метод будет будет освобождён и активность также будет удалена из кучи. Мы возвращаемся к начальной позиции.

Рассмотрим случай, когда пользователь закроет активность или повернёт экран после десяти секунд.

Задача по-прежнему выполняется, ссылка на активность по-прежнему жива и мы имеем утечку памяти.

Когда метод run() выполнится, стек освободит объекты и сборщик мусора в порядке очереди почистит объекты в куче, так как они уже не будут иметь ссылок из стека.

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

Singleton (Одиночка)

Когда происходит утечка памяти? Когда мы инициализируем синглтон в активности, то передаём ссылку на контекс-активность с долгим сроком жизни.

В этом случае ссылка на активность будет существовать пока не закроется приложение.

Чтобы избежать утечку, используйте контекст приложения, а не активности.

Или вы можете переписать класс одиночки.

Утечка с таймерами

Таймеры, которые не отменяются при выходе с экрана, тоже служат источниками утечки памяти.

Вы разработали приложение для социальной сети. В этом приложении можно обмениваться сообщениями между пользователями, и вы добавляете на экран обмена сообщениями таймер, который делает запрос на сервер каждые 10 секунд с целью получения новых сообщений, но вы забыли этот таймер выключить при выходе с экрана:

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

Фрагменты

Никогда не сохраняйте ссылки на Fragment в активности или другом фрагменте.

Активность хранит ссылки на 5-6 запущенных фрагментов даже если на экране всегда виден только один. Один фрагмент хранит ссылку на другой фрагмент. Фрагменты, видимые на экране в разное время, общаются друг с другом по прямым закешированным ссылкам. FragmentManager в таких приложениях выполняет чаще всего рудиментарную роль — в нужный момент он подменяет содержимое контейнера нужным фрагментом, а сами фрагменты в back stack не добавляются (добавление фрагмента, на который у вас есть прямая ссылка, в back stack рано или поздно приведёт к тому, что фрагмент будет выгружен из памяти; после возврата к этому фрагменту будет создан новый, а ваша ссылка продолжит ссылаться на существующий, но невидимый пользователю фрагмент).

Это очень плохой подход по целому ряду причин. Во-первых, если вы храните в активности прямые ссылки на 5-6 фрагментов, то это тоже самое, как если бы вы хранили ссылки на 5-6 Activity. Весь интерфейс, все картинки и вся логика пяти неиспользуемых фрагментов не могут быть выгружены из памяти, пока запущено Activity.

Во-вторых, эти фрагменты становится крайне сложно переиспользовать. Попробуйте перенести фрагмент в другое место программы при условии, что он должен быть обязательно запущен в одном Activity с фрагментами, x, y и z, которые переносить не надо.

Относитесь к фрагментам как к Activity. Делайте их максимально модульными, общайтесь между фрагментами только через Activity и FragmentManager.

Рассмотренные примеры — это частные случаи одного общего правила. Все утечки памяти появляются тогда и только тогда, когда вы сохраняете ссылку на объект с коротким жизненным циклом (short-lived object) в объекте с длинным жизненным циклом (long-lived object).

OutOfMemoryError — частая причина падения приложения из-за нехватки памяти. Особенно, если вы активно используете изображения.

Утечки памяти, связанные с неправильным использованием android.os.Handler. Не совсем очевидно, но все, что вы помещаете в Handler, находится в памяти и не может быть очищено сборщиком мусора в течении некоторого времени. Иногда довольно длительного. Читайте статью Борьба с утечками памяти в Android. Часть 1

Пример утечки с системными менеджерами

В Android есть много системных менеджеров (содержат слово «Manager» в именах классов), которые следует регистрировать. И часто программисты забывают снять регистрацию.

Возьмём для примера класс LocationManager, который помогает определить местоположение. Напишем минимальный код.

Запустите пример. В студии внизу выберите вкладку 6: Android Monitor (Сейчас вместо него появился Profiler), а в ней вкладку Monitors. В верхней части окна будет блок Memory, который представляет для нас интерес.

Начинайте вращать устройство с запущенным приложением. Вы увидите, что ваше приложение начинает забирать память у устройства (тёмно-синий цвет).

Мне не удалось исчерпать всю память и сломать приложение, в какой-то момент умная система освобождала занятую память и всё повторялось снова. В других ситуациях может случиться так, что память кончится раньше, чем сообразит система.

Нажмите на третью кнопку в этом окне Dump Java Heap. Данное действие сгенерирует hprof-файл, содержащий слепок памяти в заданный момент. Далее студия автоматически откроет созданный файл, который можно изучить.

Обратите внимание на вкладку Analyzer Tasks сбоку в верхнем правом углу. Откройте эту вкладку. В ней вы увидите строчку с флажком Detect Leaked Activities (Обнаружить утекающие активности). В окнеAnalysis Results щёлкните по строке Leaked Activities, чтобы увидеть дополнительную информацию.

Видно, что при поворотах создавалось множество активностей MainActivity, а вместе с ней и объект LocationManager.

Добавим код в метод onDestroy(), как это предписано документацией.

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

В Android Studio есть специальный инструмент, который позволяет следить за памятью — Profiler, запускаемый из меню View | Tool Windows. Также имеется отдельный значок инструмента на панели в верхней части. Новый инструмент заменил Android Monitor в старых версиях студии.

Запустите профайлер, появится окно с четырьмя блоками: CPU, MEMORY, NETWORK, ENERGY. Нас интересует память. Щёлкаем в этой области, чтобы оставить слежение только за используемой памятью.

Нажмите кнопку Dump Java heap, чтобы получить дамп кучи. Рядом имеется кнопка очистки мусора Force garbage collection.

Источник

Оцените статью