Многие приложения выполняют легитимные действия, когда их интерфейс не виден, например скачивают большие пакеты объектов, синхронизируют локальные базы данных, транслируют аудио, записывают тренировки или поддерживают сеанс подключенного устройства. Когда эта фоновая работа выполняется в том же процессе ОС, что и ваш основной интерфейс и графический движок, весь процесс остается на повышенном уровне важности (oom_score_adj между 0 и 250) вместе со всей памятью, выделенной интерфейсом.
В этой статье рассказывается, почему это вредит вашему приложению и устройству в целом, как перенести фоновую работу в отдельный легковесный процесс с помощью android:process и как проверить экономию памяти с помощью adb shell и Perfetto.
Почему при однопроцессной работе сохраняется память пользовательского интерфейса
Android управляет памятью и завершением процессов на уровне ОС (pid), а не отдельных компонентов (Activity, Service или BroadcastReceiver). Activity Manager (OomAdjuster) присваивает каждому процессу одно значение oom_score_adj, которое отражает наиболее важный активный компонент или входящую привязку в этом процессе:
- Когда пользователь взаимодействует с вашим приложением
Activity, процесс выполняется в состоянииTOP(oom_score_adj = 0). - Когда пользователь переходит в другое приложение и у вашего приложения нет активных фоновых компонентов, процесс перемещается в корзину
PREV(oom_score_adj = 700) илиCACHED(oom_score_adj >= 900). В кешированной корзине платформа замораживает процесс с помощью морозильника cgroup v2, сжимает его память с помощьюCachedAppOptimizerи позволяетlmkdсначала завершить процесс при нехватке памяти. - Однако если в том же процессе также выполняется активная служба (например, типа
dataSync,mediaPlayback,connectedDeviceилиlocationна уровнеoom_score_adj = 200) или размещается служба, с которой связывается система или другой важный клиент, весь процесс сохраняет повышенный приоритет даже после остановкиActivity.
Когда тяжелый процесс пользовательского интерфейса остается в oom_score_adj = 200, несколько больших категорий памяти остаются резидентными:
- Остановленный объект
Activityи иерархия View. Если объектActivityне завершен явным образом, егоWindow, дерево View, структуры макета и состояние View остаются доступными в куче Java, пока пользователь находится в другом приложении. - Нативные графические контексты и выделение ресурсов графического процессора. Стандартный объект
HardwareRendererсохраняет контекст графического процессора и кеш, а остановленный объектActivityнаходится в процессе, который не кешируется. Приложения и игровые движки, которые создают собственные контексты Vulkan (VkDevice,VkSwapchainKHR, пулы дескрипторов, кеши конвейеров) или OpenGL ES (EGLContext, текстуры, буферы вершин), также сохраняют свои структуры драйверов, шейдерные программы и графические буферы, если только движок явно не отключает графическое устройство при остановке. - Фрагментация кучи и накладные расходы во время выполнения. Даже когда приложение освобождает кеш в
onTrimMemory, частично заполненные страницы нативного распределителя, измененные страницы Zygote с копированием при записи, загруженные общие библиотеки (.so) и метаданные времени выполнения остаются в частной памяти процесса. Поскольку процесс превышает пороговое значение кеширования (oom_score_adj < 900), платформа не замораживает его и не запускает сжатие кешированных приложений. Подробнее о том, как это влияет на привязанные службы, читайте в разделе Совместное размещение привязанной службы с интерфейсом, требующим большого объема памяти.
Как оптимизация фоновых процессов защищает приложение
Если приложение в фоновом режиме использует столько же памяти, сколько и в активном, это может привести к следующим проблемам:
- Ограничения на использование памяти в фоновом режиме строже, чем в активном. В Android 17 и более поздних версиях Memory Limiter применяет к приложению меньший лимит памяти not visible, когда ни один из его процессов не показывает видимый интерфейс, в том числе во время работы активной службы. Ограничение распространяется на все процессы приложения. Если процесс, в котором выполняется активная служба, по-прежнему удерживает память пользовательского интерфейса и графическую память, эта память учитывается в меньшем лимите. Когда приложение превышает лимит, ядро ограничивает его работу, освобождая страницы памяти. Это замедляет скачивание, синхронизацию или воспроизведение. Если приложение продолжит выделять память после того, как раздел подкачки будет исчерпан, система завершит его работу. Чтобы узнать, затронуло ли это ваше приложение, ознакомьтесь с разделом Ограничения на использование памяти приложениями.
- При нехватке памяти в первую очередь завершаются более крупные процессы. Когда
lmkdдолжна освободить память и настроена на выбор самого тяжелого процесса в корзине важности (ro.lmk.kill_heaviest_task), процесс активной службы, который также содержит сотни мегабайт памяти пользовательского интерфейса, завершается раньше, чем меньшие процессы той же важности, и ваша фоновая работа останавливается вместе с ним. Подробнее о взаимодействии kswapd и lmkd… - Кешированное состояние интерфейса можно заморозить, сжать и записать обратно во флеш-память. Если вы разрешите переместить состояние пользовательского интерфейса переднего плана в кэшированный процесс, ОС сможет заморозить процесс, сжать его память в zRAM и записать холодные страницы обратно во флэш-память. Объем потребляемой памяти уменьшается, а состояние интерфейса остается неизменным. Это позволяет сохранить фоновые процессы и кешированный интерфейс до возвращения пользователя.
- Бюджеты памяти для отдельных процессов становятся практичными. Процесс, который выполняет только фоновую работу, имеет небольшой предсказуемый рабочий набор, поэтому вы можете задать для него строгий бюджет памяти, не ограничивая интерфейс.
Запуск второго процесса может создать впечатление, что общий объем потребляемой памяти увеличивается, поскольку у второй среды выполнения есть небольшая базовая стоимость. Это сравнение справедливо только в том случае, если один процесс может полностью освободить весь свой пользовательский интерфейс и графическое состояние при переходе к фоновой работе, что приложения и наборы инструментов пользовательского интерфейса не могут надежно сделать на практике. Если разрешить ОС замораживать, сжимать и записывать обратно отдельный кэшированный процесс интерфейса, в большинстве случаев будет использоваться гораздо меньше физической памяти. Это позволит не превышать лимит для невидимых приложений и снизит количество завершений работы из-за нехватки памяти как для вашего приложения, так и для всего устройства.
Изолируйте фоновую работу в отдельном процессе
Чтобы избежать задержки интерфейса и графики во время фоновой работы, запустите долгоживущие фоновые компоненты в выделенном легковесном процессе, оставив интерфейс и графический движок в основном процессе:
| Процесс | Объявление в манифесте | Размещенные компоненты | Состояние, когда пользователь выходит из интерфейса во время фоновой работы |
|---|---|---|---|
| Основной процесс пользовательского интерфейса | По умолчанию (com.example.app) |
Activity, представления интерфейса, кеши изображений, средство визуализации Vulkan или OpenGL ES, аудио и движок сцены.
|
Падает до PREV, а затем до CACHED (oom_score_adj >= 900). Замораживается, сжимается и записывается обратно во флеш-память.
|
| Фоновый рабочий процесс | android:process=":bg" |
Активный режим (Service), фоновый загрузчик объектов, синхронизация, управление подключенными устройствами
|
Выполняется в FGS (oom_score_adj = 200) только с собственной кучей и кодом воркера, без представлений пользовательского интерфейса и контекстов графического процессора.
|
Объявите бюджеты процессов и памяти
Назначьте фоновый процесс Service частному подпроцессу, задав android:process=":bg" в AndroidManifest.xml. Двоеточие (:) в начале создает частный процесс, ограниченный UID вашего приложения.
В Android 17 QPR2 (уровень API 37.2) и более поздних версиях объедините разделение процессов с бюджетами памяти приложения для каждого процесса внутри блока <processes>, чтобы операционная система устанавливала жесткий потолок рабочего набора для фонового рабочего процесса, не ограничивая процесс пользовательского интерфейса переднего плана:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".HeavyApplication"
android:label="@string/app_name">
<!-- App-wide budgets that cover every process, including :bg -->
<memory-budget android:maxMb="512" />
<memory-budget
android:maxMb="128"
android:state="background" />
<processes>
<!-- Tight budget for the headless background worker process -->
<process android:process=":bg">
<memory-budget android:maxMb="32" />
</process>
</processes>
<activity
android:name=".MainActivity"
android:exported="true" />
<service
android:name=".AssetDownloadService"
android:process=":bg"
android:foregroundServiceType="dataSync"
android:exported="false" />
</application>
</manifest>
Память, используемая в :bg, учитывается как в собственном бюджете, так и в бюджете всего приложения. На устройствах с версиями до Android 17 QPR2 система игнорирует <memory-budget>, а android:process=":bg" работает на всех уровнях API.
Инициализация шлюза в Application.onCreate
Android создает экземпляр класса Application и вызывает Application.onCreate в каждом процессе, порожденном для вашего пакета. Распространенная ошибка при внедрении android:process=":bg" – оставлять тяжелые наборы инструментов пользовательского интерфейса, пулы загрузки изображений, Analytics SDK или инициализацию нативного движка (System.loadLibrary) внутри незащищенного метода Application.onCreate. В противном случае процесс :bg будет раздут за счет того же базового объема памяти, которого вы пытаетесь избежать.
Проверьте Application.getProcessName (доступно в Android 9, API уровня 28, и более поздних версиях) в onCreate и инициализируйте только зависимости, необходимые для текущего процесса:
Kotlin
class HeavyApplication : Application() {
override fun onCreate() {
super.onCreate()
val processName = Application.getProcessName()
if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
initBackgroundWorkerProcess()
return
}
initMainUiProcess()
}
private fun initBackgroundWorkerProcess() {
// Initialize only lightweight HTTP, storage, and sync primitives.
// Avoid loading native rendering libraries or allocating UI caches.
}
private fun initMainUiProcess() {
// Initialize UI frameworks, image caches, and native engines.
System.loadLibrary("heavy_engine_jni")
}
companion object {
private const val BG_PROCESS_SUFFIX = ":bg"
}
}
Java
public final class HeavyApplication extends Application {
private static final String BG_PROCESS_SUFFIX = ":bg";
@Override
public void onCreate() {
super.onCreate();
String processName = Application.getProcessName();
if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
initBackgroundWorkerProcess();
return;
}
initMainUiProcess();
}
private void initBackgroundWorkerProcess() {
// Initialize only lightweight HTTP, storage, and sync primitives.
// Avoid loading native rendering libraries or allocating UI caches.
}
private void initMainUiProcess() {
// Initialize UI frameworks, image caches, and native engines.
System.loadLibrary("heavy_engine_jni");
}
}
Библиотеки, которые инициализируются через поставщика контента, например Jetpack App Startup, инициализируются только в основном процессе, поскольку поставщик контента, объявленный без android:process, выполняется в основном процессе. Если код в :bg зависит от такой библиотеки, инициализируйте ее явным образом в initBackgroundWorkerProcess.
Если вы используете игровой движок, например Unity, реализуйте фоновую службу в обычной библиотеке или плагине Android, написанных на Java или Kotlin и не ссылающихся на классы движка, такие как UnityPlayer. В этом случае процесс :bg не загружает нативные библиотеки движка. Чтобы убедиться, что процесс завершен, проверьте, что в столбце adb shell cat /proc/<pid>/maps для процесса :bg не указано libunity.so или libil2cpp.so.
Общение без закрепления процесса пользовательского интерфейса
Если пользователь открыл MainActivity, пока AssetDownloadService работает в :bg, в интерфейсе часто нужно показывать ход скачивания. Важность процесса передается от клиента к процессу, в котором размещен используемый сервис или поставщик контента, поэтому направление каждой зависимости имеет значение.
- Привязка интерфейса к исполнителю безопасна. Когда
MainActivityпривязывается кAssetDownloadService, система повышает важность:bgдо уровня видимого интерфейса. Когда пользователь уходит, процесс интерфейса кешируется и привязка больше ничего не повышает. - Рабочий процесс, зависящий от процесса интерфейса, небезопасен. Если код в
:bgпривязывается к сервису или запрашивает поставщика контента, который работает в основном процессе, система повышает важность основного процесса до уровня важности рабочего процесса, и память интерфейса снова закрепляется. Вместо этого зарегистрируйте в интерфейсе обратный вызов с помощью воркера, напримерMessengerили интерфейса обратного вызова AIDL. Передача объекта связывателя в качестве обратного вызова не меняет важность ни одного из процессов.
Отмените регистрацию обратного вызова перед отменой привязки, чтобы рабочий процесс никогда не вызывал процесс пользовательского интерфейса после того, как система его заморозит. В следующем примере привязка к рабочему процессу выполняется, только когда виден интерфейс:
Kotlin
class MainActivity : Activity() {
private var downloadService: IBinder? = null
private val downloadConnection = object : ServiceConnection {
override fun onServiceConnected(
name: ComponentName,
service: IBinder
) {
downloadService = service
// Register a Messenger or AIDL callback for progress updates.
}
override fun onServiceDisconnected(name: ComponentName) {
// The binding stays active and reconnects if the worker restarts.
downloadService = null
}
}
override fun onStart() {
super.onStart()
val intent = Intent(this, AssetDownloadService::class.java)
bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
if (downloadService != null) {
// Unregister the progress callback here, before unbinding.
downloadService = null
}
// Unbind the ServiceConnection even if onServiceConnected hasn't run
// yet.
unbindService(downloadConnection)
}
}
Java
public final class MainActivity extends Activity {
private IBinder downloadService;
private final ServiceConnection downloadConnection =
new ServiceConnection() {
@Override
public void onServiceConnected(
ComponentName name, IBinder service) {
downloadService = service;
// Register a Messenger or AIDL callback for progress
// updates.
}
@Override
public void onServiceDisconnected(ComponentName name) {
// The binding stays active and reconnects if the worker
// restarts.
downloadService = null;
}
};
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, AssetDownloadService.class);
bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (downloadService != null) {
// Unregister the progress callback here, before unbinding.
downloadService = null;
}
// Unbind the ServiceConnection even if onServiceConnected hasn't run
// yet.
unbindService(downloadConnection);
}
}
Подробнее о том, как флаги привязки влияют на oom_score_adj в разных процессах, рассказывается в разделе Привязки сервисов и состояния процессов.
Безопасная координация общего состояния на диске
Поскольку основной процесс и :bg выполняются в отдельных адресных пространствах с отдельными средами выполнения и дескрипторами файлов:
- SQLite и Room. Используйте упреждающую запись в журнал (режим ведения журнала по умолчанию в Room на большинстве устройств), чтобы оба процесса могли одновременно читать и записывать базу данных. Не вызывайте
enableMultiInstanceInvalidationиз:bg. Room размещает свой сервис аннулирования в основном процессе, поэтому рабочий процесс будет привязан к основному процессу и сохранит память пользовательского интерфейса на повышенном уровне важности рабочего процесса. Вместо этого настройте отправку отчетов об изменениях через обратный вызов, который регистрирует пользовательский интерфейс, и повторный запрос к базе данных при запуске интерфейса. - Настройки. Не используйте
SharedPreferencesв нескольких процессах. Используйте DataStore в многопроцессном коде или передавайте состояние явным образом через IPC. - Файлы объектов. Запишите скачанные пакеты объектов в промежуточный файл в
:bg, вызовитеfdatasync, а затемposix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)в:bgпосле записи (как показано в разделе Возврат страниц памяти операционной системе) и атомарно переименуйте файл. Пусть пользовательский интерфейс получит результат при следующем запуске и привязке к рабочему процессу, а не вызывает процесс пользовательского интерфейса, который может быть заморожен.
Проверьте результат в adb shell
Чтобы убедиться, что разделение процессов на фоновые и основные изолирует повышенное использование памяти, проверьте состояния процессов Activity Manager и dumpsys meminfo после перехода на главный экран. Пример выходных данных в этом разделе получен из примера приложения MemoryLab, которое выполняет одно и то же нативное выделение 64 МБ в процессе пользовательского интерфейса в обеих конфигурациях и размещает свою разделенную активную службу в процессе :remote.
1. Сравнение состояний процессов и оценок OOM
Запустите фоновую рабочую нагрузку, нажмите кнопку главного экрана, чтобы остановить Activity, и отправьте запрос в Activity Manager:
adb shell dumpsys activity processes com.example.app | grep -E "Proc #|oom:|state:"
В однопроцессной архитектуре основной процесс остается запущенным, даже если его Activity остановлен. В течение примерно 15 секунд после того, как пользователь покидает приложение, Activity Manager сохраняет процесс, в котором выполняется активная служба, на уровне oom_score_adj = 50 (fg-service-act), а затем понижает его до 200 (fg-service). Следующие выходные данные были получены через 20 секунд после нажатия кнопки главного экрана:
Proc #1: prcp F/S/FGS ---NFUATI t: 0 13794:com.android.memorylab/u0a284 (fg-service)
oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
state: cur=FGS set=FGS lastRss=0.00 lastCachedRss=0.00
В архитектуре с разделением процессов только рабочий процесс остается в сегменте oom_score_adj = 200, а основной процесс пользовательского интерфейса переходит в сегмент PREV (oom_score_adj = 700), а затем в сегмент кешированных процессов (oom_score_adj >= 900).
Proc #3: prcp F/S/FGS ---NFUATI t: 0 13544:com.android.memorylab:remote/u0a284 (fg-service)
oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
state: cur=FGS set=FGS lastRss=0.00 lastCachedRss=0.00
Proc #1: prev b/ /LAST -------TI t: 0 13312:com.android.memorylab/u0a284 (previous)
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=0.00 lastCachedRss=0.00
По истечении времени ожидания предыдущего приложения основной процесс перемещается в кешированную группу (cch, oom_score_adj = 900, previous-expired) и теряет все свои возможности (---------), включая неявное процессорное время (I). В этот момент система замораживает его и может в любой момент сжать или освободить его, не прерывая рабочий процесс.
2. Сравнение dumpsys meminfo в разных процессах
Сравните сводку dumpsys meminfo для рабочего процесса с повышенными привилегиями с процессом пользовательского интерфейса:
adb shell dumpsys meminfo com.example.app:bg
adb shell dumpsys meminfo com.example.app
Пример выходных данных App Summary для рабочего процесса MemoryLab в oom_score_adj = 200:
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 380 13840
Native Heap: 1548 2832
Code: 132 55816
Stack: 200 208
Graphics: 0 0
Private Other: 496
System: 1926
Unknown: 4168
TOTAL PSS: 4682 TOTAL RSS: 76864 TOTAL SWAP PSS: 441
Пример выходных данных App Summary для процесса MemoryLab UI в oom_score_adj = 700, который содержит нативное выделение памяти 64 МБ в Private Other:
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 1316 19816
Native Heap: 30408 31596
Code: 3148 115796
Stack: 656 664
Graphics: 0 0
Private Other: 67044
System: 13687
Unknown: 71092
TOTAL PSS: 116259 TOTAL RSS: 238964 TOTAL SWAP PSS: 259
Рабочему процессу требуется менее 5 МБ PSS, поскольку он использует большую часть своего кода и страниц среды выполнения совместно с Zygote. В приложении, которое выполняет рендеринг с помощью Vulkan или OpenGL ES, строка Graphics процесса пользовательского интерфейса также включает память графического процессора.
Этот пример был получен на виртуальном устройстве Cuttlefish с программным рендерингом, поэтому Graphics – это 0 в обоих процессах.
3. Проверить учет cgroup для каждого процесса
У каждого процесса с вашим UID есть собственный каталог cgroup v2 в родительской группе cgroup для вашего приложения. Следующая команда выводит список oom_score_adj, memory.current и memory.high для каждого процесса вашего приложения:
adb shell '
for PID in $(pidof com.example.app com.example.app:bg); do
CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
echo "pid=$PID ($(tr -d "\0" < /proc/$PID/cmdline))" \
"adj=$(cat /proc/$PID/oom_score_adj)" \
"memory.current=$(cat $CG/memory.current)" \
"app.memory.high=$(cat ${CG%/*}/memory.high)"
done
'
Пример выходных данных MemoryLab после нажатия кнопки главного экрана:
pid=15301 (com.android.memorylab) adj=700 memory.current=103260160 app.memory.high=966787072
pid=16518 (com.android.memorylab:remote) adj=200 memory.current=3608576 app.memory.high=966787072
Рабочий процесс занимает менее 4 МБ, а процесс интерфейса – около 98 МБ. У каждого процесса есть собственный memory.current, но ограничитель памяти устанавливает лимит для родительской группы cgroup, которая содержит все процессы вашего приложения. В обеих строках указано одно и то же значение app.memory.high – 966 787 072 байта. Это невидимый лимит в 922 МБ, который показан в выходных данных ниже.
Чтобы посмотреть видимые и невидимые ограничения, которые Memory Limiter применяет на устройстве, выполните следующую команду:
adb shell am memory-limiter status
Пример выходных данных для устройства с 4 ГБ ОЗУ, где лимит для невидимых процессов составляет половину лимита для видимых:
Memory limiter
enabled limits=true killing=true ignored=none
visible: memHigh=1843MB memMax=2048MB swapMax=1024MB
not-visible: memHigh=922MB memMax=1024MB swapMax=1024MB
Как посмотреть результат в Perfetto
Perfetto позволяет визуализировать хронологию oom_score_adj и RSS в обоих процессах, когда пользователь переходит от интерфейса переднего плана к главному экрану, пока продолжается фоновая работа.
1. Как записать трассировку
Запишите 15-секундную трассировку, которая фиксирует счетчики памяти процесса, память графического процессора и изменения oom_score_adj при запуске фонового задания в приложении и переходе на главный экран.
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/process-splitting.perfetto-trace <<'EOF'
buffers: {
size_kb: 32768
fill_policy: DISCARD
}
data_sources: {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 250
}
}
}
data_sources: {
config {
name: "android.gpu.memory"
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "oom/oom_score_adj_update"
ftrace_events: "gpu_mem/gpu_mem_total"
}
}
}
duration_ms: 15000
EOF
adb pull /data/misc/perfetto-traces/process-splitting.perfetto-trace
2. Проверьте дорожки процессов в Perfetto UI
Откройте трассировку в Perfetto UI и разверните группы процессов для com.example.app и com.example.app:bg.
- Закрепите дорожки
oom_score_adjиmem.rss.anonдля обоих процессов, чтобы они отображались рядом в верхней части временной шкалы. - Найдите временную метку, когда пользователь нажимает кнопку главного экрана:
- В рабочем процессе
oom_score_adjостается на уровне200, аmem.rss.anon– на низком уровне. - В процессе пользовательского интерфейса
oom_score_adjподнимается с0до200, остается там около секунды, покаActivityостанавливается, а затем поднимается до700(PREV). По истечении времени ожидания предыдущего приложения оно перемещается в кэшированный диапазон, где система может сжать и освободить память, не затрагивая рабочий процесс.
- В рабочем процессе
На рисунке 2 процесс пользовательского интерфейса ненадолго задерживается на 200, пока его Activity останавливается, а затем поднимается до 700, в то время как рабочий процесс остается на 200 с примерно 19 МБ анонимного RSS. Для сравнения на рисунке 3 показана 35-секундная трассировка конфигурации с совместным размещением, где один процесс остается на уровне 50, а затем 200 со всей памятью пользовательского интерфейса.
Чтобы изучить эти представления, откройте разделенную трассировку и совмещенную трассировку в Perfetto UI.
3. Как запросить повышенный объем потребляемой памяти с помощью PerfettoSQL
Выполните следующий запрос PerfettoSQL на вкладке Query (SQL) (Запрос (SQL)), чтобы измерить средний размер анонимного и файлового RSS, удерживаемого каждым процессом вашего приложения на каждом уровне oom_score_adj:
INCLUDE PERFETTO MODULE android.memory.process;
SELECT
process_name,
score AS oom_score_adj,
ROUND(SUM(dur) / 1e9, 2) AS duration_sec,
ROUND(SUM(anon_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_anon_mb,
ROUND(SUM(file_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_file_mb
FROM memory_oom_score_with_rss_and_swap_per_process
WHERE process_name GLOB 'com.example.app*'
GROUP BY process_name, score
ORDER BY process_name, score;
Пример выходных данных для разделенной трассировки на рисунке 2:
process_name oom_score_adj duration_sec avg_anon_mb avg_file_mb
com.android.memorylab 0 4.11 93.3 110.4
com.android.memorylab 200 1.06 126.8 110.5
com.android.memorylab 700 9.72 115.1 110.5
com.android.memorylab:remote 0 0.01 NULL NULL
com.android.memorylab:remote 200 12.87 18.9 53.7
Пример выходных данных для трассировки, показанной на рисунке 3:
process_name oom_score_adj duration_sec avg_anon_mb avg_file_mb
com.android.memorylab 0 4.10 96.0 110.4
com.android.memorylab 50 16.04 117.6 110.5
com.android.memorylab 200 14.68 116.5 110.6
В разделенной конфигурации только около 19 МБ анонимной памяти остается в oom_score_adj = 200 после первой секунды. При совмещенной конфигурации около 117 МБ остается в 50, а затем в 200, пока работает активная служба.
← Возврат страниц памяти ОС | ↑ Наверх