En Android 17 y versiones posteriores, la plataforma rige la memoria de los procesos del sistema con dos mecanismos complementarios de cgroup v2 de Linux que funcionan junto con el limitador de memoria general de la aplicación:
- Límites mínimos de protección de daemons del sistema (
SystemMemoryBudgetManagerconmemory.min): Las reservas dememory.minde cgroup v2 con ajuste automático (establecidas en el 90% del conjunto de trabajo residente establecido de cada daemon y limitadas de forma global al 10% de la RAM física total) protegen los daemons interactivos críticos y los procesos de shell (system_server,surfaceflinger, la HAL de gráficoscomposer,audioserver,cameraserver,netd,SystemUIy el selector de la pantalla principal) bajo presión de memoria, lo que reemplaza la fijación estática de archivosPinnerServicemlock. - Límites de memoria de las apps del sistema (
<memory-budget>conmemory.high): Los límites flexibles de cgroup v2memory.highcalibrados para la carga de trabajo (por lo general, de 30 MB a 190 MB) declarados en los archivos de manifiesto para las apps del sistema persistentes y en segundo plano (Bluetooth,Nfc,CalendarProvider,Traceur,MediaProvider,:PhotoPicker,NetworkStack,AppSearchyTeleService) recuperan de forma proactiva la caché de páginas de archivos inactivos y el montón anónimo posterior a la ráfaga antes de que se produzca una presión de memoria global.
En la siguiente tabla, se comparan los dos mecanismos:
| Dimensión | Límites de protección del daemon del sistema | Límites de memoria de las apps del sistema |
|---|---|---|
| Servicio de plataforma | SystemMemoryBudgetManager |
MemoryLimiter |
| Control de cgroup v2 | Piso de protección rígido de memory.min |
Límite de recuperación parcial de memory.high |
| Procesos de destino | Daemons interactivos con latencia crítica y la IU de shell (system_server, surfaceflinger, HAL de composer, audioserver, cameraserver, netd, SystemUI y la app de Home) |
Apps del sistema persistentes y sin interfaz gráfica, proveedores de contenido y servicios en segundo plano (como Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch y TeleService) |
| Modelo de dimensionamiento | Ajuste automático en el tiempo de ejecución (90% del conjunto de trabajo establecido, con un límite global del 10% de la RAM total) sin tablas de ajuste por dispositivo | Atributos XML declarativos del manifiesto de <memory-budget> calibrados por nivel de RAM, estado del proceso y recuento de píxeles de la pantalla |
| Beneficio principal | Evita los tirones de la IU interactiva y las detenciones de activación cuando hay presión de memoria mientras se reemplazan las listas de archivos estáticos PinnerService mlock |
Expulsa continuamente la caché de archivos de un solo uso inactivos y el montón posterior a la ráfaga para dejar más RAM física para las apps en primer plano y almacenadas en caché |
Para obtener más información sobre la sintaxis de <memory-budget>, los atributos XML, las APIs de entorno de ejecución y la contabilización de la memoria del SO, consulta Cómo establecer presupuestos de memoria de la app y Cómo comprender y solucionar problemas de la memoria de Android.
Protege los daemons interactivos críticos con SystemMemoryBudgetManager
SystemMemoryBudgetManager mantiene los procesos que se encuentran directamente en la ruta interactiva del usuario residentes en la RAM física durante la presión de memoria. En lugar de fijar una lista estática de archivos APK, ODEX y VDEX en la memoria con mlock (como lo hace PinnerService para config_defaultPinnerServiceFiles y config_pinnerHomePinBytes), SystemMemoryBudgetManager asigna límites mínimos de memory.min de cgroup v2 dinámicos a los siguientes procesos:
- Porción del sistema principal (
/sys/fs/cgroup/system):system_server,surfaceflinger, la HAL de gráficoscomposer,audioserver,cameraserverynetd, además de cualquier daemon del sistema nativo o del proveedor específico del factor de forma que se indique enconfig_systemMemoryBudgetAdditionalSystemProcesses(por ejemplo,android.hardware.perceptionhostyauracamerahalen dispositivos de RE). - Porción de app interactiva (
/sys/fs/cgroup/apps): Es el proceso principal del paquete SystemUI configurado (config_systemUi) y el proceso principal del titular del rol de página principal del usuario actual.
Proteger el contenedor de rol de inicio junto con config_systemUi garantiza la cobertura en todos los factores de forma. En los factores de forma en los que config_systemUi es un paquete de compatibilidad liviano (como Wear OS, en el que la cara de reloj, las notificaciones y las tarjetas se ejecutan dentro de la app de Home), la shell para el usuario sigue recibiendo protección completa del conjunto de trabajo. Si el intent de Home se resuelve en el paquete config_systemUi, el proceso se protege una vez. Solo el proceso principal de cada paquete (processName == packageName) recibe un piso de protección, por lo que los subprocesos auxiliares (como :screenshot) no consumen presupuesto de protección.
Política de ajuste automático y delegación de cgroup de tres niveles
Debido a que el conjunto de trabajo de un daemon interactivo varía ampliamente según los factores de forma y las capacidades de RAM (por ejemplo, SystemUI usa alrededor de 45 MB en un reloj inteligente, 60 MB en un teléfono de 6 GB y 180 MB en un teléfono de 16 GB), las tablas de consulta estáticas por dispositivo son frágiles. En cambio,
SystemMemoryBudgetManager deriva los límites inferiores directamente de la huella residente de cada proceso
con tres constantes:
| Parámetro de política | Valor predeterminado | Comportamiento |
|---|---|---|
| Precio mínimo por proceso | 90% de memory.current establecido menos la caché de archivos no asignados |
Protege el montón anónimo, la losa y el código asignado activo, mientras que deja el 10% de la cola más inactiva recuperable para que las páginas realmente inactivas se puedan expulsar. La caché de archivos sin asignar (max(0, file - shmem - file_mapped) de memory.stat) se resta antes de aplicar la proporción del 90% para que la caché de análisis de paquetes de inicio o las lecturas de recursos únicas no aumenten los límites mínimos. |
| Límite de RAM global | 10% de la RAM total (config_systemMemoryBudgetLowKb) |
Coincide con la protección config_pinnerMaxPinnedMemoryPercentage de PinnerService. Si la suma de los precios mínimos del 90% supera el 10% de MemTotal, todos los precios mínimos se reducen proporcionalmente por el mismo factor. |
| Frecuencia de actualización | 1, 4, 16 y 64 s después del inicio, y luego cada 30 min | Una serie de estabilización geométrica converge en el conjunto de trabajo posterior al inicio de cada daemon sin retrasos de preparación codificados por dispositivo, y una actualización de estado estable de 30 minutos hace un seguimiento del crecimiento y la reducción graduales del conjunto de trabajo durante la vida útil del dispositivo. |
En el controlador de memoria de cgroup v2 de Linux
(mm/memcontrol.c:effective_protection), la protección memory.min efectiva de un cgroup de hoja
está limitada por la memory.min configurada en sus cgroups superiores
y principales. Establecer memory.min solo en un directorio pid_<pid> de hoja
mientras uid_<uid>/memory.min es 0 genera una protección efectiva nula.
Por el contrario, establecer un memory.min plano solo en el segmento /sys/fs/cgroup/system superior distribuye la protección no reclamada en todos los UID secundarios y procesa de forma proporcional (memory_recursiveprot), lo que permite que los procesos de diagnóstico o secundarios que comparten uid_1000 (android.uid.system) desvíen la protección de surfaceflinger y audioserver.
Para proporcionar un aislamiento exacto por daemon, SystemMemoryBudgetManager escribe
protección en los tres niveles de la jerarquía de cgroup v2 en cada actualización,
como se muestra en la figura 1:
- Nodo de proceso de hoja (
pid_<pid>/memory.min): Se establece en el límite inferior del conjunto de trabajo del 90% (escalado) del proceso. - Nodo de UID (
uid_<uid>/memory.min): Se establece en la suma exacta de sus pisos de hojapid_*administrados, por lo que los procesos no administrados que comparten ese UID (como otros daemons deAID_SYSTEMo paquetes desharedUserId="android.uid.system") tienenmemory.min = 0y no pueden reclamar protección. - Porción de nivel superior (
/sys/fs/cgroup/system/memory.miny/sys/fs/cgroup/apps/memory.min): Se establece en la suma exacta de sus elementos secundariosuid_*. Los valores dememory.lowinactivos en los nodos administrados se borran a0, y los directoriosuid_*que se administraron en una actualización anterior (como un selector de página principal anterior después de que el usuario cambia de selector) se restablecen a0.
Figura 1: Delegación de piso de protección de tres niveles en las porciones de cgroup.
Por qué se usa memory.min en lugar de memory.low en los kernels de MGLRU
En la LRU multigeneracional (MGLRU, el motor de recuperación de páginas predeterminado en los kernels de Android 6.6 y versiones posteriores), memory.low y memory.min se comportan de manera diferente en mm/vmscan.c:shrink_one:
memory.low(aplazamiento de un solo pase): Un cgroup cuyo uso es inferior amemory.lowse rota al final del LRU de memcg (MEMCG_LRU_TAIL) y se omite para un solo pase de recuperación. En el siguiente pase con presión de memoria sostenida, el kernel recupera el cgroup por completo y registra un eventoMEMCG_LOW. En las pruebas de esfuerzo de dispositivos físicos con un acaparador de memoria anónimo que crece hasta el 85% de la RAM física,memory.lowpermitió queSystemUIen un reloj inteligente se redujera de ~45 MB a ~5 a 10 MB yaudioserveren un dispositivo plegable de 16 GB disminuyera de 15 MB a 2 MB en comparación con un piso de ~14 MB.memory.min(límite de protección estricto): La recuperación global omite un cgroup cuyo uso es inferior amemory.min. Como se muestra en la figura 2, con la misma carga de trabajo de presión de RAM del 85%,memory.minsolo descarta el 10% más frío de cada daemon y mantieneSystemUI,surfaceflingeryaudioserveren sus pisos del 90% con cero fallas anónimas y sin aumento en las finalizaciones de procesos delmkd.
memory.min proporciona una reserva física estricta que coincide con la protección de la fijación de PinnerService mlock que reemplaza, con la ventaja de que la LRU del kernel (en lugar de una lista de archivos estática) elige qué páginas de código, montón y gráficos activos permanecen residentes.
Figura 2: Retención del conjunto de trabajo del daemon bajo una presión de memoria creciente.
Precalentamiento de inicio y recuperación de caché de archivos no asignados después del inicio
Reemplazar el mlock permanente de PinnerService de
config_defaultPinnerServiceFiles requiere coordinar dos comportamientos
en el tiempo de inicio:
- Preparación previa de inicio en segundo plano de un solo intento (
PinnerService): OmitirPinnerServicemlocksin preparar la caché de la página cambia ~95 MB de lecturas de disco de APK, ODEX y VDEX deSystemUIa la ruta de inicio síncrona cuando se iniciaSystemUI(OnBootPhase_550), lo que agrega de 300 a 450 ms a la finalización del inicio. No basta con llamar amadvise(MADV_WILLNEED), ya queforce_page_cache_raen el kernel limita una sola llamada aMADV_WILLNEEDa una ventana de lectura anticipada (por lo general, de 128 KB a 2 MB). En cambio, cuandoSystemMemoryBudgetManager.shouldSkipDefaultSystemPinningestá activo,PinnerServiceasigna cada archivo enconfig_defaultPinnerServiceFilesen un subproceso en segundo plano duranteonStart, propaga de forma síncrona el archivo en la caché de la página conOs.mlocky llama de inmediato aOs.munlock,munmapyclose. Esto preserva el tiempo de inicio rápido y, al mismo tiempo, deja cero bytes bloqueados de forma permanente para que las páginas de inicio en frío se puedan expulsar más adelante. - Excluye y recupera la caché de análisis de paquetes de inicio de
system_server: Durante el inicio,PackageManagerServiceanaliza los APK instalados, lo que deja entre 1.5 GB y 2.5 GB de caché de páginas de archivos sin asignar en frío que se cargan en el cgroup desystem_server. Si se incluye en el cálculo dememory.mindesystem_server, esa caché de inicio en frío satura el límite global del 10% de la RAM y reduce proporcionalmente los límites de protección desurfaceflinger,SystemUIyaudioserver. Para evitar la falta de límite,SystemMemoryBudgetManagerleememory.statparasystem_server, calcula la caché de archivos sin asignar comomax(0, file - shmem - file_mapped)y resta esa caché sin asignar dememory.currentantes de calcular el piso del 90%, como se muestra en la figura 3. Además, 10 segundos después dePHASE_BOOT_COMPLETED, el administrador realiza una escritura única de<unmapped_file> swappiness=0en el nodomemory.reclaimdesystem_serveren Linux 6.2 y kernels más recientes para expulsar de forma proactiva la caché de análisis de paquetes inactiva sin intercambiar el montón anónimo ni expulsar las páginas de código y DEX asignadas. En los kernels más antiguos (comoandroid14-6.1) que rechazanswappiness=0conEINVAL, el administrador omite la escrituramemory.reclaimen lugar de recurrir a una escritura de recuperación no calificada que intercambiaría el montón anónimo durante el inicio.
Figura 3: Se agregó la convergencia del piso de protección y la recuperación de la caché después del inicio.
Requisitos del kernel y superposiciones de configuración del dispositivo
SystemMemoryBudgetManager se basa en mem_cgroup_calculate_protection en el
kernel de Linux para propagar memory.min en cgroups anidados. Los kernels comunes de Android android15-6.6 y android16-6.12 incluyen compatibilidad completa con la protección anidada de MGLRU de forma predeterminada. Los dispositivos que ejecutan android14-6.1 con MGLRU habilitado requieren tres backports upstream en mm/vmscan.c y mm/memcontrol.c para que los cgroups de UID y porción intermedia vacíos propaguen la protección a los cgroups de procesos de hoja:
30d77b7eef01(mm/mglru: fix ineffective protection calculation)8aa420617918(mm/mglru: respect min_ttl_ms with memcgs)adb8213014b2(mm: memcg: fix stale protection of reclaim target memcg)
Los OEM pueden personalizar o inhabilitar SystemMemoryBudgetManager con las siguientes superposiciones de recursos del framework:
config_systemMemoryBudgetLowKb(número entero, KB):0(predeterminado) limita la suma de todos los límites mínimos dememory.minal 10% de la RAM física total. Un valor positivo establece un límite explícito en KB, y un valor negativo inhabilitaSystemMemoryBudgetManageren el dispositivo (lo que conserva la fijación estáticamlockheredada dePinnerService).config_systemMemoryBudgetHighKb(número entero, KB):0(predeterminado) deja/sys/fs/cgroup/system/memory.highenmax. Un valor positivo establece un límite máximo de porción superiormemory.high(debe ser mayor o igual que el límite mínimo).config_systemMemoryBudgetAdditionalSystemProcesses(array de cadenas): Enumera nombres de procesos daemon adicionales del sistema nativo o del proveedor que son críticos para la latencia en/sys/fs/cgroup/system(coincide con/proc/<pid>/commo el nombre base deargv[0]en/proc/<pid>/cmdlinepara los UID del sistema y del proveedor en10000) para proteger con un 90% de pisos dememory.minjunto con los seis daemons del sistema predeterminados. Usa esta superposición en lugar de configurarmemory.mineninit.rcporqueSystemMemoryBudgetManagervuelve a calcularuid_<uid>/memory.mincomo la suma de sus procesos de hoja administrados y borra los nodosuid_*no administrados en cada actualización. Por ejemplo, un dispositivo de RE puede proteger sus daemons de HAL de cámara y seguimiento espacial con la siguiente superposición:<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(cadena): Es el nombre del paquete de la app de SystemUI de la plataforma, resuelto conMATCH_SYSTEM_ONLY.
Para inspeccionar los niveles de SystemMemoryBudgetManager activos y los nodos de cgroup en un dispositivo, ejecuta lo siguiente:
# Inspect policy state, resolved PIDs, and applied floors in dumpsys meminfo adb shell dumpsys meminfo | sed -n '/System Memory Budgets/,$p'# Inspect raw cgroup v2 memory.min nodes across the system and apps slices adb shell head -n 1 \ /sys/fs/cgroup/system/memory.min \ /sys/fs/cgroup/system/uid_*/memory.min \ /sys/fs/cgroup/system/uid_*/pid_*/memory.min \ /sys/fs/cgroup/apps/memory.min \ /sys/fs/cgroup/apps/uid_*/memory.min \ /sys/fs/cgroup/apps/uid_*/pid_*/memory.min
Vincula las apps del sistema persistentes y en segundo plano con presupuestos de memoria
Si bien SystemMemoryBudgetManager protege los daemons interactivos de la recuperación con límites mínimos de memory.min, muchas otras apps del sistema y servicios de plataforma con privilegios se ejecutan en segundo plano durante períodos prolongados como procesos PERSISTENT, servicios en primer plano vinculados o proveedores de contenido que se consultan con frecuencia.
Sin límites de memoria explícitos, estos procesos no se restringen hasta que
alcanzan los límites de varios gigabytes del limitador de memoria (como de 4 GB
a 10 GB para las apps visibles y de 2 GB a 5 GB para las apps
en segundo plano) o la presión de memoria global activa la actividad de kswapd y de eliminación de memoria baja (lmkd) en todo el sistema.
Si no se restringen, los procesos del sistema en segundo plano suelen acumular grandes cantidades de memoria inactiva que no forma parte de su conjunto de trabajo activo:
- Caché de páginas de archivos sin límites: Los servicios que indexan contenido multimedia, buscan bases de datos o sincronizan tablas de proveedores y calendarios (como
AppSearch,MediaProvideryCalendarProvider) asignan o transmiten archivos grandes en el disco. Sin un límite de cgroupmemory.high, las páginas limpias respaldadas por archivos de lecturas únicas permanecen cargadas en el cgroup del proceso de forma indefinida, lo que consume cientos de megabytes de RAM física. - Retención de montón anónimo posterior a la ráfaga: Los servicios que experimentan aumentos repentinos de actividad transitorios, como las ráfagas de descubrimiento de audio y BLE de
Bluetooth, la validación de TLS del portal cautivo deNetworkStacko los asistentes de IU que pasan a segundo plano, pueden retener páginas de montón anónimas inactivas mucho después de que se complete la ráfaga.
Aplicar declaraciones <memory-budget> calibradas (límites flexibles de memory.high, por lo general, de 30 MB a 190 MB) a las apps del sistema proporciona dos beneficios principales:
- Recuperación de páginas proactiva y localizada: Cuando un proceso del sistema supera su presupuesto de
memory.highconfigurado, el kernel de Linux dirige la recuperación de páginas específicamente a ese cgroup, lo que expulsa primero las páginas respaldadas por archivos inactivos y comprime las páginas anónimas inactivas en ZRAM sin esperar a que se produzca una presión de memoria global del dispositivo. - Más RAM disponible para las apps en primer plano y el grupo de apps almacenadas en caché: Limitar las cachés de archivos inactivos en los servicios del sistema siempre activos recupera de decenas a cientos de megabytes de RAM física en todo el dispositivo, lo que reduce los tirones de la IU, disminuye la presión de inicio de la cámara y mantiene activas más apps del usuario en el estado almacenado en caché.
Configura los presupuestos de memoria de las apps del sistema
Las apps del sistema usan la misma sintaxis de manifiesto <memory-budget>, cascada de estado del proceso, escalamiento de densidad de pantalla y regla de resolución de la última cláusula coincidente que las apps generales. Para obtener la lista completa de atributos, consulta la referencia de atributos XML.
Los OEM pueden declarar elementos <memory-budget> directamente dentro del archivo AndroidManifest.xml de una app del sistema o combinarlos desde un archivo en formato XML independiente con la propiedad additional_manifests en Android.bp. Por convención, el AOSP almacena los presupuestos de las apps del sistema de referencia en archivos *-budget.xml independientes (ya sea en frameworks/base/core/memory_budgets/ para los paquetes de la plataforma principal o junto con módulos individuales, como packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/ y packages/services/Telephony/) y los adjunta con additional_manifests:
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
Alcance a nivel del paquete (UID) frente al alcance a nivel del proceso (PID)
Si declaras un presupuesto de referencia a nivel del paquete (<application>) o un presupuesto de varios procesos (<processes><process>), se determinará si el kernel aplica memory.high a nivel del cgroup del UID (/sys/fs/cgroup/uid_<uid>/memory.high) o a nivel del cgroup por PID (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high), como se muestra en la figura 4:
- Presupuesto a nivel del paquete (
<application>): Se usa cuando el APK ejecuta un solo proceso con su propio UID dedicado (por ejemplo,Bluetooth,Nfc,NetworkStack,CalendarProvider,TraceuroAppSearch). - Presupuesto a nivel de proceso (
<processes><process>): Se usa cuando el APK comparteandroid:sharedUserId(comoandroid.uid.phonecon el UID1001oandroid.uid.systemcon el UID1000) con otros paquetes del sistema para que otros procesos que comparten ese UID no se limiten juntos, o cuando un solo APK ejecuta varios procesos con diferentes funciones de memoria (por ejemplo, el daemonMediaProvideren segundo plano y el subproceso de IU:PhotoPicker).
Figura 4: Límites de memoria a nivel de UID frente a nivel de PID
En el siguiente ejemplo, se muestra MediaProvider-budget.xml, que limita los presupuestos con niveles de RAM al proceso MediaProvider predeterminado (<process>) y un presupuesto ajustado a la pantalla al proceso de IU :PhotoPicker. Cada atributo android:maxDeviceRamMb coincide con los dispositivos cuya RAM física total (MemTotal) es igual o inferior al umbral de MB especificado (o se aplica a cualquier capacidad de RAM cuando se omite). Las cláusulas se ordenan desde el valor predeterminado sin restricciones
hasta el umbral de maxDeviceRamMb más pequeño, de modo que la
regla de resolución de última cláusula coincidente selecciona
el nivel aplicable más estricto:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.media.module">
<uses-sdk android:targetSdkVersion="10000" />
<application>
<processes>
<process>
<memory-budget
android:maxMb="85" />
<memory-budget
android:maxMb="75"
android:maxDeviceRamMb="12288" />
<memory-budget
android:maxMb="60"
android:maxDeviceRamMb="8192" />
</process>
<process android:process=":PhotoPicker">
<memory-budget
android:maxMb="85"
android:additionalBytesPerDisplayPixel="25" />
</process>
</processes>
</application>
</manifest>
Cómo se calibraron los presupuestos de referencia de AOSP en los dispositivos Google Pixel
Los presupuestos de memoria de la app de referencia del sistema en el AOSP se calibraron en todos los dispositivos Google Pixel con una RAM física de 6 GB a 16 GB (incluidos teléfonos, plegables y tablets de 6 GB, 8 GB, 12 GB y 16 GB) y resoluciones de pantalla que van de ~1.2 MP (720 × 1600) a ~4.5 MP (2076 × 2152).
La calibración combinó dos fuentes de datos complementarias: la telemetría de campo de cgroup v2 en toda la flota y los barridos de memory.high de dispositivos físicos controlados en los recorridos críticos del usuario (CUJ).
Análisis de telemetría de cgroup v2 en toda la flota
Se recopilaron instantáneas periódicas de cgroup v2 en toda la flota de dispositivos y se dividieron por nivel de RAM física (≤8 GB, 12 GB y ≥16 GB) y bucket de estado del proceso (foreground, perceptible, background y cached). El análisis estadístico se basó en seis principios:
- Descompón
memory.currenten un conjunto de trabajo en comparación con la caché de archivos recuperables: Debido a quememory.highrige la carga de cgroup residente (memory.current), cada instantánea se descompuso conmemory.staten un montón anónimo (anon), páginas anónimas intercambiadas (swap) y caché de páginas respaldada por archivos (file), como se muestra en la figura 5. La demanda anónima incompresible total (anon + swapen P99) establece el piso mínimo del conjunto de trabajo para el presupuesto, de modo que el proceso no corra el riesgo de agotar la ZRAM, mientras que una gran brecha entrememory.currentP99 yanon + swapP99 indica una caché de páginas de archivos quememory.highpuede reclamar, siempre que la caché no esté asignada y esté inactiva. - Ten en cuenta la memoria que
memory.highno puede recuperar: No toda la memoria de archivos y compartida es caché inactiva. Las páginas que están bloqueadas (unevictableenmemory.stat, como la memoria compartida bloqueada conmlocko la RAM de invitado bloqueada de una máquina virtual cuyos procesos de host se cobran al cgroup de la app) establecen un límite mínimo en el presupuesto, y las páginas asignadas (file_mapped) suelen formar parte del conjunto de trabajo activo. Verificaunevictable,shmemyfile_mappedantes de tratar la memoria de archivos como recuperable. Por ejemplo, alrededor de 100 MB de memoriaAppSearchestán bloqueados y no se pueden expulsar, por lo que su presupuesto cubre ese mínimo más su conjunto de trabajo de host activo, con espacio para que crezca la región bloqueada. - Filtrar la dilución del estado inactivo: Los proveedores de contenido (
CalendarProvider) y las herramientas para desarrolladores on demand (Traceur) pasan del 70% al 99.9% de las instantáneas de campo inactivas en ~1 MB a 3 MB. Calcular los percentiles sin procesar en todas las instantáneas diluye de forma significativa las estimaciones de P95 y P99. En su lugar, los percentiles y los intervalos de confianza se calcularon en función de las instantáneas activas sin diluir (filtrando las instantáneas deCACHEDy las instantáneas de referencia inactivas). - Usa intervalos de confianza agrupados por dispositivo: Como un solo dispositivo
proporciona muchas instantáneas periódicas a lo largo del tiempo, tratar las instantáneas como
muestras independientes subestima la incertidumbre de la cola. Todas las estimaciones del conjunto de trabajo de P95 y P99 se evaluaron con intervalos de confianza conservadores del 95% agrupados por dispositivo (con el recuento de dispositivos distintos
N_devcomo el tamaño de la muestra efectivo). - Ten en cuenta las ráfagas transitorias con
memory.peak: El muestreo periódico puede pasar por alto ráfagas de asignación alta de corta duración (como la validación de TLS del portal cautivo deNetworkStacky los sondeos de DNS simultáneos, o la inicialización de A2DP y BLE deBluetooth). Para los servicios con ráfagas, los presupuestos se dimensionaron para cubrir los percentiles 90 a 95 de los niveles máximos dememory.peakdel kernel durante su vida útil en los niveles de RAM. - Confirma que un nivel de RAM refleje la RAM, no la generación del dispositivo: Los dispositivos más nuevos suelen tener más RAM y más funciones, por lo que un conjunto de trabajo que crece con el nivel de RAM podría estar creciendo con las funciones. Antes de agregar
un nivel de
maxDeviceRamMb, divide los datos por modelo de dispositivo. Por ejemplo,com.android.phonealcanza el mismo P99anon + swap(de ~85 a 90 MB) en teléfonos de 12 GB y 16 GB de la misma generación, mientras que un dispositivo plegable más antiguo de 12 GB usa 40 MB. Por lo tanto, su presupuesto usa un solo valor sin nivel de RAM, de modo que un dispositivo con menos RAM que incluya las funciones de telefonía más recientes no tenga un presupuesto insuficiente.
Figura 5: Conjunto de trabajo frente a caché de archivos recuperable.
CUJ de dispositivos físicos y barridos de escalamiento de pantalla
Para validar los presupuestos derivados de la telemetría y calibrar los presupuestos proporcionales a la pantalla (como :PhotoPicker) y los presupuestos en segundo plano específicos del estado (como Traceur), se ejecutaron barridos de memory.high en vivo en dispositivos físicos que abarcan de 6 GB a 16 GB de RAM:
- Cómo encontrar el punto de inflexión de cero fallas repetidas: Mientras se ejecutan CUJ de extremo a extremo (como el desplazamiento rápido por la cuadrícula de fotos, la exploración de álbumes, el deslizamiento de vistas previas de fotos y videos de alta resolución en pantalla completa, y el registro y guardado de seguimientos), se redujo
memory.highmientras se medían los contadores de cgroupmemory.stat(workingset_refault_anon,workingset_refault_file,pgscanypgsteal) ymemory.events(high). Como se ilustra en la figura 6, el presupuesto óptimo se encuentra justo por encima del punto de inflexión de cero fallas repetidas. El punto de inflexión de cero fallas de referencia es el umbral en el que el kernel recupera la caché de archivos de un solo uso inactivos (pgsteal > 0) sin activar la hiperpaginación del conjunto de trabajo activo (workingset_refault == 0). - Validación del escalamiento de píxeles de pantalla en todas las resoluciones: Las pruebas de
:PhotoPickeren cuatro resoluciones de pantalla físicas y emuladas, desde ~1.2 MP (720 × 1,600) hasta ~4.5 MP (pantalla interna desplegada de 2,076 × 2,152), confirmaron que su conjunto de trabajo de mapas de bits activo de Glide y ViewPager2 se escala de forma lineal con el recuento de píxeles de la pantalla. La línea de base sin pantalla también varía según el dispositivo: con casi la misma cantidad de píxeles, el punto de inflexión sin fallas fue de alrededor de 140 MB en la pantalla externa de un dispositivo plegable de 16 GB, en comparación con 110 a 125 MB en un teléfono de 6 GB. Un valor de referencia de 85 MB más 25 bytes por píxel de pantalla (android:maxMb="85"yandroid:additionalBytesPerDisplayPixel="25") se mantiene por encima del punto de inflexión medido en cada pantalla probada (~110 MB en HD+, ~145 MB en FHD+ y ~190 MB en pantallas desplegadas). - Verificación de la recolección de elementos no utilizados de ART en estados en segundo plano: En cgroup v2,
las páginas de imágenes de framework y
boot.artcompartidas por Zygote permanecen cargadas en el cgroup de Zygote y se tratan como un espacio inmune durante la recolección de elementos no utilizados de compactación de marcas (CMC) simultánea de ART. Las pruebas de esfuerzo confirmaron que, cuando una app de la IU del sistema pasa abackground, ajustarmemory.high(por ejemplo, a 30 MB enTraceur) limita de forma limpia el montón privado y la caché de páginas sin causar un uso excesivo de la recolección de elementos no utilizados ni actividad de intercambio.
Figura 6: Encontrar el punto de inflexión de cero fallas de referencia durante un CUJ
Resumen de los presupuestos de las apps del sistema de referencia del AOSP
En la siguiente tabla, se resumen los presupuestos de referencia calibrados en AOSP en los niveles de RAM y los estados de los procesos:
| Paquete o proceso del sistema | Alcance | ≤8 GB de RAM | 12 GB de RAM | ≥16 GB de RAM | Controlador de calibración principal |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80 MB | 95 MB | 110 MB | Cubre ráfagas de memory.peak de por vida del P90 (de ~75 a 110 MB) y >2x de memory.current activo del P99. |
com.android.nfc |
UID (<application>) |
50 MB | 60 MB | 60 MB | Abarca la emulación de tarjeta de host activa P99 (hasta ~50 MB) y P90 a P95 memory.peak mientras se reduce la caché de archivos inactivos. |
com.android.providers.calendar |
UID (<application>) |
70 MB predeterminado (primer plano) 50 MB perceptible y background |
El conjunto de trabajo activo es invariable en todos los niveles de RAM. El presupuesto en segundo plano mantiene un margen anónimo para las alarmas de recordatorio, a la vez que reduce la caché de páginas de SQLite durante las sincronizaciones. | ||
com.android.traceur |
UID (<application>) |
85 MB de forma predeterminada (en primer plano y perceptible) 30 MB background |
Conserva un margen completo de 85 MB durante la grabación y exportación de registros activos (TraceService), mientras que limita el estado inactivo y de transmisión a 30 MB. |
||
com.android.providers.media.module (principal) |
PID (<process>) |
60 MB | 75 MB | 85 MB | Proporciona un margen de ~10 a 30 MB sobre el P99 de anon + swap mientras se recupera la caché de archivos de miniaturas y SQLite inactivos. |
com.android.providers.media.module:PhotoPicker |
PID (<process>) |
85 MB + 25 bytes por píxel (aproximadamente 110 MB en HD+, 145 MB en FHD+ y 190 MB en dispositivos plegables de ~4.5 MP) |
Se escala de forma proporcional con el recuento de píxeles de la pantalla (additionalBytesPerDisplayPixel="25") para que coincida con el punto de inflexión del conjunto de trabajo de mapas de bits de Glide. |
||
com.android.networkstack |
UID (<application>) |
65 MB | 70 MB | 85 MB | Memoria predominantemente anónima, con un tamaño de memory.peak de vida útil de P95 para cubrir las ráfagas de TLS, DNS y DHCP del portal cautivo. |
com.android.appsearch.apk |
UID (<application>) |
130 MB | Cubre ~100 MB de memoria bloqueada y no expulsable, además de un conjunto de trabajo de host activo de ~5 MB, con margen para que crezca la región bloqueada. Se midieron cero fallas de página repetidas con ≥125 MB mientras se recuperaba la caché de páginas frías (hasta ~330 MB en P99). | ||
com.android.phone (TeleService) |
PID (<process>) |
120 MB | Se limita por PID en el UID compartido 1001. El conjunto de trabajo hace un seguimiento de la generación del dispositivo en lugar de la RAM, por lo que un solo presupuesto se encuentra ~30 MB por encima del P99 anon + swap más alto por modelo. |
||
Cómo los OEM pueden recalibrar los presupuestos de las apps del sistema para sus propios dispositivos
Los OEM que personalizan las apps del sistema (por ejemplo, agregan perfiles de Bluetooth de proveedores, extensiones de telefonía personalizadas o indexadores de contenido multimedia de OEM) o envían servicios del sistema preinstalados adicionales pueden recalibrar los presupuestos existentes o crear nuevos archivos *-budget.xml para su cartera de hardware.
Recopila perfiles de memoria de cgroup v2 activos
Para cada proceso del sistema de destino, lee sus nodos de memoria de cgroup v2 en /sys/fs/cgroup/uid_<uid>/pid_<pid>/ en las cargas de trabajo y los niveles de RAM representativos:
memory.current: Memoria residente actual que se cobra al cgroup (sin incluir las páginas compartidas de Zygote)memory.peak: Es el nivel máximo dememory.currentdesde el inicio del proceso.memory.swap.current: Uso actual del espacio de intercambio comprimido en ZRAMmemory.stat: Desglose deanon,file,pgscan,pgsteal,workingset_refault_anonyworkingset_refault_filememory.events: Es el recuento acumulativo de incumplimientos dehigh(eventos de limitación y de reclamo dememory.high).
Cuando agregues telemetría de dispositivos de prueba interna o de campo, separa los estados de proceso activos (TOP, FOREGROUND_SERVICE, vinculaciones activas y sincronizaciones activas) de los estados CACHED o PERSISTENT inactivos para que las instantáneas inactivas no desvíen hacia abajo tus cálculos de conjunto de trabajo P95 y P99.
Separa el montón incompresible de la caché de archivos recuperable
Compara el percentil 99 de anon + swap con el percentil 99 de
memory.current:
- Servicios dominados por la caché de archivos (
file≫anon, como proveedores de bases de datos, indexadores de búsqueda y daemons de registro): Estableceandroid:maxMbpara proporcionar un margen cómodo (por lo general, de 1.5x a 4x) sobre el P99anon + swapmás una ventana de trabajo para las páginas de archivos activos. Esta configuración permite que el kernel desaloje de forma continua las páginas de archivos fríos sin asignar sin tocar el montón anónimo. Siunevictableofile_mappedson grandes (como con la memoria bloqueada o las bases de datos asignadas a la memoria), cuenta esa huella como conjunto de trabajo también. - Servicios con ráfagas o dominados por montones anónimos (
anonconstituye la mayor parte dememory.current, como las pilas de redes o conectividad): Ajusta el tamaño deandroid:maxMbpara cubrir del P90 al P95 dememory.peak, de modo que las ráfagas transitorias no activen el intercambio síncrono de ZRAM en subprocesos sensibles a la latencia. - Procesos de IU con uso intensivo de la pantalla (como selectores, galerías o asistentes de IU del sistema): Mide el espacio de referencia que no es de IU y el espacio de mapa de bits incremental por megapíxel en las pantallas de resolución más baja y más alta, y expresa el presupuesto con
android:maxMbmásandroid:additionalBytesPerDisplayPixel.
Barrido de memory.high en dispositivos físicos
Antes de confirmar un presupuesto nuevo para la compilación, usa am memory-limiter manual (o escribe directamente en el nodo cgroup memory.high del proceso en una compilación userdebug) para probar los límites de candidatos mientras ejecutas tus pruebas de CUJ automatizadas:
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MBSupervisa memory.stat y memory.events antes y después del CUJ:
- El presupuesto es demasiado bajo:
workingset_refault_anonoworkingset_refault_fileaumentan de forma repentina durante el CUJ,memory.swap.currentcrece de forma continua durante el trabajo activo en primer plano, o bien se produce una regresión en la latencia de Binder o del fotograma del CUJ. Aumentar el presupuesto más allá del punto de inflexión predeterminado - El presupuesto es demasiado alto:
memory.events:highsigue siendo0incluso después de cargas de trabajo intensas de transmisión o indexación de archivos, yfileenmemory.statcrece sin límites con páginas inactivas. Ajusta el presupuesto para el conjunto de trabajo activo. - El presupuesto está bien calibrado: El
memory.currentde estado estable se mantiene por debajo dememory.highdurante el funcionamiento normal. Durante ráfagas intensas de archivos o de caché,pgstealrecupera páginas de archivos inactivos, mientras queworkingset_refault_anonpermanece cerca de cero y la latencia de CUJ no cambia.