Android 17 이상에서 플랫폼은 일반 애플리케이션 메모리 제한기와 함께 작동하는 두 가지 보완적인 Linux cgroup v2 메커니즘을 사용하여 시스템 프로세스 메모리를 관리합니다.
- 시스템 데몬 보호 하한 (
memory.min가 있는SystemMemoryBudgetManager): 자체 조정 cgroup v2memory.min예약 (각 데몬의 정착된 상주 작업 세트의 90%로 설정되고 전체 물리적 RAM의 10% 로 전역적으로 제한됨)은 메모리 부족 시 중요한 대화형 데몬 및 셸 프로세스(system_server,surfaceflinger, 그래픽composerHAL,audioserver,cameraserver,netd,SystemUI, 홈 런처)를 보호하여 정적PinnerServicemlock파일 고정을 대체합니다. - 시스템 앱 메모리 상한 (
memory.high를 사용하는<memory-budget>): 지속 및 백그라운드 시스템 앱 (Bluetooth,Nfc,CalendarProvider,Traceur,MediaProvider,:PhotoPicker,NetworkStack,AppSearch,TeleService)의 매니페스트 파일에 선언된 워크로드 보정 cgroup v2memory.high소프트 한도 (일반적으로 30MB~190MB)는 전역 메모리 부족이 발생하기 전에 콜드 파일 페이지 캐시와 버스트 후 익명 힙을 사전에 회수합니다.
다음 표에서는 두 메커니즘을 비교합니다.
| 측정기준 | 시스템 데몬 보호 하한선 | 시스템 앱 메모리 상한 |
|---|---|---|
| 플랫폼 서비스 | SystemMemoryBudgetManager |
MemoryLimiter |
| cgroup v2 제어 | memory.min 하드 보호 하한선 |
memory.high 소프트 회수 상한 |
| 대상 프로세스 | 지연 시간에 민감한 대화형 데몬 및 셸 UI (system_server, surfaceflinger, composer HAL, audioserver, cameraserver, netd, SystemUI, Home 앱) |
지속 및 헤드리스 시스템 앱, 콘텐츠 제공업체, 백그라운드 서비스 (예: Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch, TeleService) |
| 사이징 모델 | 기기별 조정 테이블이 없는 런타임 시 자체 조정 (정착된 작업 세트의 90%, 전체 RAM의 10% 로 전역 제한) | RAM 등급, 프로세스 상태, 디스플레이 픽셀 수로 보정된 선언적 <memory-budget> 매니페스트 XML 속성 |
| 주요 이점 | 정적 PinnerService mlock 파일 목록을 대체하는 동안 메모리 부족 시 대화형 UI 버벅거림 및 대기 모드 해제 지연을 방지합니다. |
콜드 일회성 파일 캐시와 버스트 후 힙을 지속적으로 제거하여 포그라운드 및 캐시된 앱에 더 많은 물리적 RAM을 남깁니다. |
<memory-budget> 문법, XML 속성, 런타임 API, OS 메모리 계산에 관한 자세한 내용은 앱 메모리 예산 설정 및 Android 메모리 이해 및 문제 해결을 참고하세요.
SystemMemoryBudgetManager로 중요한 대화형 데몬 보호
SystemMemoryBudgetManager는 메모리 부족 시 사용자의 상호작용 경로에 직접 있는 프로세스를 실제 RAM에 상주하도록 유지합니다. mlock를 사용하여 APK, ODEX, VDEX 파일의 정적 목록을 메모리에 고정하는 대신 (PinnerService가 config_defaultPinnerServiceFiles 및 config_pinnerHomePinBytes에 하는 것처럼) SystemMemoryBudgetManager는 동적 cgroup v2 memory.min 하한을 다음 프로세스에 할당합니다.
- 핵심 시스템 슬라이스 (
/sys/fs/cgroup/system):system_server,surfaceflinger, 그래픽composerHAL,audioserver,cameraserver,netd및config_systemMemoryBudgetAdditionalSystemProcesses에 나열된 폼 팩터별 네이티브 시스템 또는 공급업체 데몬 (예: XR 기기의android.hardware.perceptionhost및auracamerahal) - 상호작용 앱 슬라이스 (
/sys/fs/cgroup/apps): 구성된 SystemUI 패키지 (config_systemUi)의 기본 프로세스 및 현재 사용자의 홈 역할 보유자의 기본 프로세스입니다.
config_systemUi와 함께 홈 역할 소유자를 보호하면 모든 폼 팩터에 적용됩니다. config_systemUi가 경량 호환성 패키지인 폼 팩터 (예: 워치 페이스, 알림, 타일이 Home 앱 내에서 실행되는 Wear OS)에서는 사용자 대상 셸이 여전히 전체 작업 세트 보호를 받습니다. 홈 인텐트가 config_systemUi 패키지로 확인되면 프로세스가 한 번 보호됩니다. 각 패키지의 기본 프로세스 (processName == packageName)만 보호 기준을 수신하므로 보조 하위 프로세스 (예: :screenshot)는 보호 예산을 사용하지 않습니다.
자체 조정 정책 및 3단계 cgroup 위임
대화형 데몬의 작업 세트는 폼 팩터와 RAM 용량에 따라 크게 다르므로 (예: SystemUI는 스마트시계에서 약 45MB, 6GB 휴대전화에서 60MB, 16GB 휴대전화에서 180MB 사용) 기기별 정적 참고표는 취약합니다. 대신 SystemMemoryBudgetManager는 세 가지 상수를 사용하여 각 프로세스의 자체 상주 메모리 사용량에서 직접 하한을 도출합니다.
| 정책 매개변수 | 기본값 | 동작 |
|---|---|---|
| 프로세스별 하한선 | 해결된 memory.current의 90% 에서 매핑되지 않은 파일 캐시 제외 |
익명 힙, 슬랩, 핫 매핑된 코드를 보호하는 동시에 가장 콜드한 10% 테일을 회수 가능하게 유지하여 진정으로 콜드한 페이지를 계속 제거할 수 있습니다. 매핑되지 않은 파일 캐시 (memory.stat 중 max(0, file - shmem - file_mapped))는 90% 비율을 적용하기 전에 차감되므로 부팅 패키지 스캔 캐시 또는 일회성 애셋 읽기가 하한선을 부풀리지 않습니다. |
| 전역 RAM 한도 | 총 RAM의 10%(config_systemMemoryBudgetLowKb) |
PinnerService config_pinnerMaxPinnedMemoryPercentage 가드레일과 일치합니다. 90% 하한선의 합계가 MemTotal의 10% 를 초과하면 모든 하한선이 동일한 비율로 축소됩니다. |
| 새로고침 주기 | 시작 후 1초, 4초, 16초, 64초, 이후 30분마다 | 기하학적 정착 시리즈는 기기별 워밍업 지연을 하드코딩하지 않고 각 데몬의 시작 후 작업 세트에 수렴하며, 30분간의 안정 상태 새로고침은 기기 수명 동안 점진적인 작업 세트 증가 및 축소를 추적합니다. |
Linux cgroup v2 메모리 컨트롤러(mm/memcontrol.c:effective_protection)에서 리프 cgroup의 유효한 memory.min 보호는 상위 및 상위 cgroup에 구성된 memory.min에 의해 제한됩니다. uid_<uid>/memory.min가 0인 동안 리프 pid_<pid> 디렉터리에만 memory.min를 설정하면 보호가 적용되지 않습니다.
반대로 최상위 /sys/fs/cgroup/system 슬라이스에만 고정 memory.min를 설정하면 미사용 보호가 모든 하위 UID 및 프로세스에 비례하여 분산되므로 (memory_recursiveprot) uid_1000를 공유하는 진단 또는 보조 프로세스 (android.uid.system)가 surfaceflinger 및 audioserver에서 보호를 가져올 수 있습니다.
데몬별로 정확한 격리를 제공하기 위해 SystemMemoryBudgetManager는 그림 1과 같이 새로고침할 때마다 cgroup v2 계층 구조의 세 가지 수준 모두에서 보호를 작성합니다.
- 리프 프로세스 노드 (
pid_<pid>/memory.min): 프로세스의 (확장된) 90% 작업 세트 하한으로 설정합니다. - UID 노드 (
uid_<uid>/memory.min): 관리되는pid_*리프 플로어의 정확한 합계로 설정되므로 해당 UID를 공유하는 관리되지 않는 프로세스 (예: 다른AID_SYSTEM데몬 또는sharedUserId="android.uid.system"패키지)는memory.min = 0를 가지며 보호를 요청할 수 없습니다. - 최상위 슬라이스 (
/sys/fs/cgroup/system/memory.min및/sys/fs/cgroup/apps/memory.min):uid_*하위 요소의 정확한 합계로 설정됩니다. 관리 노드의 비활성memory.low값은0로 삭제되고 이전 새로고침에서 관리된uid_*디렉터리 (예: 사용자가 런처를 전환한 후의 이전 홈 런처)는0로 재설정됩니다.
그림 1. cgroup 슬라이스 전반의 3단계 보호 하한 위임
MGLRU 커널에서 memory.low 대신 memory.min이 사용되는 이유
다세대 LRU (MGLRU, Android 6.6 이상 커널의 기본 페이지 회수 엔진)에서 memory.low 및 memory.min는 mm/vmscan.c:shrink_one에서 다르게 동작합니다.
memory.low(단일 패스 지연): 사용량이memory.low미만인 cgroup은 memcg LRU (MEMCG_LRU_TAIL)의 끝으로 순환되고 단일 회수 패스에서 건너뜁니다. 지속적인 메모리 부족 상태에서 다음 패스가 실행되면 커널은 cgroup을 완전히 회수하고MEMCG_LOW이벤트를 로깅합니다. 익명 메모리 호그가 실제 RAM의 85% 까지 증가하는 실제 기기 스트레스 테스트에서memory.low는 스마트시계의SystemUI가 약 45MB에서 약 5~10MB로 줄어들고 16GB 폴더블의audioserver가 약 14MB 하한선 대비 15MB에서 2MB로 줄어들도록 허용했습니다.memory.min(엄격한 보호 하한선): 사용량이memory.min미만인 cgroup은 전역 회수에서 건너뜁니다. 그림 2와 같이 동일한 85% RAM 압력 워크로드에서memory.min는 각 데몬의 가장 콜드한 10% 테일만 삭제하고SystemUI,surfaceflinger,audioserver를 90% 하한선으로 유지하며 익명 리폴트가 없고lmkd프로세스 종료가 증가하지 않습니다.
memory.min는 대체하는 PinnerService mlock 고정의 보호와 일치하는 엄격한 물리적 예약을 제공하며, 커널 LRU (정적 파일 목록이 아님)가 상주하는 핫 코드, 힙, 그래픽 페이지를 선택한다는 이점이 있습니다.
그림 2. 메모리 압박이 증가하는 상황에서 데몬 작업 세트 보존
부팅 사전 준비 및 부팅 후 매핑되지 않은 파일 캐시 회수
config_defaultPinnerServiceFiles의 PinnerService 영구 mlock를 바꾸려면 두 가지 부팅 시간 동작을 조정해야 합니다.
- 원샷 백그라운드 부팅 사전 준비 (
PinnerService): 페이지 캐시를 준비하지 않고PinnerServicemlock를 건너뛰면SystemUI가 시작될 때 (OnBootPhase_550) 약 95MB의SystemUIAPK, ODEX, VDEX 디스크 읽기가 동기 부팅 경로로 이동하여 부팅 완료에 300~450ms가 추가됩니다. 커널의force_page_cache_ra가 단일MADV_WILLNEED호출을 하나의 미리 읽기 창 (일반적으로 128KB~2MB)으로 고정하므로madvise(MADV_WILLNEED)호출로는 충분하지 않습니다. 대신SystemMemoryBudgetManager.shouldSkipDefaultSystemPinning가 활성 상태이면PinnerService는onStart중에 백그라운드 스레드에서config_defaultPinnerServiceFiles의 각 파일을 매핑하고,Os.mlock를 사용하여 파일을 페이지 캐시에 동기식으로 채우고,Os.munlock,munmap,close를 즉시 호출합니다. 이렇게 하면 빠른 부팅 타이밍이 유지되고 영구적으로 잠긴 바이트가 남지 않으므로 나중에 콜드 스타트업 페이지를 제거할 수 있습니다. system_server부팅 패키지 검사 캐시 제외 및 회수: 부팅 중에PackageManagerService는 설치된 APK를 검사하여system_servercgroup에 청구된 1.5~2.5GB의 콜드 매핑되지 않은 파일 페이지 캐시를 남깁니다.system_server의memory.min계산에 포함된 경우 콜드 부팅 캐시는 10% 글로벌 RAM 한도를 포화시키고surfaceflinger,SystemUI,audioserver의 보호 하한을 비례적으로 축소합니다. 한도 부족을 방지하기 위해SystemMemoryBudgetManager는system_server의memory.stat를 읽고 매핑되지 않은 파일 캐시를max(0, file - shmem - file_mapped)로 계산하며, 그림 3과 같이 90% 하한을 계산하기 전에memory.current에서 매핑되지 않은 캐시를 뺍니다. 또한PHASE_BOOT_COMPLETED후 10초가 지나면 관리자는 Linux 6.2 이상 커널에서system_server의memory.reclaim노드에<unmapped_file> swappiness=0를 일회성으로 작성하여 익명 힙을 스왑하거나 매핑된 DEX 및 코드 페이지를 제거하지 않고 콜드 패키지 스캔 캐시를 사전에 제거합니다.EINVAL로swappiness=0를 거부하는 이전 커널 (예:android14-6.1)에서 관리자는 부팅 중에 익명 힙을 스왑하는 부적격 회수 쓰기로 대체하는 대신memory.reclaim쓰기를 건너뜁니다.
그림 3. 부팅 후 보호 하한선 수렴 및 캐시 회수
커널 요구사항 및 기기 설정 오버레이
SystemMemoryBudgetManager는 Linux 커널의 mem_cgroup_calculate_protection를 사용하여 중첩된 cgroup에 memory.min를 전파합니다. Android 일반 커널 android15-6.6 및 android16-6.12에는 기본적으로 전체 MGLRU 중첩 보호 지원이 포함되어 있습니다. MGLRU가 사용 설정된 android14-6.1를 실행하는 기기에는 mm/vmscan.c 및 mm/memcontrol.c에 세 개의 업스트림 백포트가 필요하므로 빈 중간 슬라이스 및 UID cgroup이 보호를 리프 프로세스 cgroup에 전파합니다.
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)
OEM은 다음 프레임워크 리소스 오버레이를 사용하여 SystemMemoryBudgetManager를 맞춤설정하거나 사용 중지할 수 있습니다.
config_systemMemoryBudgetLowKb(정수, KB):0(기본값)은 모든memory.min하한의 합계를 총 물리적 RAM의 10% 로 제한합니다. 양수 값은 명시적 한도를 KB 단위로 설정하고 음수 값은 기기에서SystemMemoryBudgetManager를 사용 중지합니다 (기존PinnerService정적mlock고정 유지).config_systemMemoryBudgetHighKb(정수, KB):0(기본값)은/sys/fs/cgroup/system/memory.high를max로 유지합니다. 양수 값은 상위 슬라이스memory.high상한을 설정합니다 (하한보다 크거나 같아야 함).config_systemMemoryBudgetAdditionalSystemProcesses(문자열 배열):/sys/fs/cgroup/system에 추가 지연 시간에 민감한 네이티브 시스템 또는 공급업체 데몬 프로세스 이름을 나열합니다 (10000아래의 시스템 및 공급업체 UID의 경우/proc/<pid>/cmdline의/proc/<pid>/comm또는argv[0]의 기본 이름과 일치). 기본 시스템 데몬 6개와 함께 90%memory.min하한으로 보호합니다.SystemMemoryBudgetManager는uid_<uid>/memory.min를 관리되는 리프 프로세스의 합계로 다시 계산하고 새로고침할 때마다 관리되지 않는uid_*노드를 지우므로init.rc에서memory.min를 설정하는 대신 이 오버레이를 사용합니다. 예를 들어 XR 기기는 다음 오버레이를 사용하여 공간 추적 및 카메라 HAL 데몬을 보호할 수 있습니다.<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(문자열): 플랫폼 SystemUI 앱의 패키지 이름으로,MATCH_SYSTEM_ONLY로 확인됩니다.
기기에서 활성 SystemMemoryBudgetManager 플로어 및 cgroup 노드를 검사하려면 다음을 실행합니다.
# 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
메모리 예산이 있는 영구 및 백그라운드 시스템 앱 바인딩
SystemMemoryBudgetManager는 memory.min 하한을 사용하여 대화형 데몬을 회수로부터 보호하지만, 다른 많은 시스템 앱과 권한이 있는 플랫폼 서비스는 PERSISTENT 프로세스, 바인딩된 포그라운드 서비스 또는 자주 쿼리되는 콘텐츠 제공업체로 백그라운드에서 장시간 실행됩니다.
명시적인 메모리 상한이 없으면 이러한 프로세스는 멀티 기가바이트 메모리 제한기 한도 (예: 표시되는 앱의 경우 4GB~10GB, 백그라운드 앱의 경우 2GB~5GB)에 도달하거나 전역 메모리 부족으로 인해 시스템 전체의 kswapd 및 메모리 부족 킬러 (lmkd) 활동이 트리거될 때까지 제한되지 않습니다.
제한되지 않은 상태로 두면 백그라운드 시스템 프로세스는 활성 작업 세트에 포함되지 않는 대량의 콜드 메모리를 누적하는 경우가 많습니다.
- 제한 없는 파일 페이지 캐시: 미디어를 색인 생성하거나 데이터베이스를 검색하거나 캘린더 및 제공업체 테이블을 동기화하는 서비스 (예:
AppSearch,MediaProvider,CalendarProvider)는 디스크의 대용량 파일을 통해 매핑하거나 스트리밍합니다. cgroupmemory.high상한이 없으면 일회성 읽기에서 가져온 클린 파일 지원 페이지가 프로세스 cgroup에 무기한으로 청구되어 수백 메가바이트의 물리적 RAM을 소비합니다. - 버스트 후 익명 힙 보관:
Bluetooth오디오 및 BLE 검색 버스트,NetworkStack종속 포털 TLS 유효성 검사, 백그라운드로 전환되는 UI 도우미와 같이 일시적인 활동 급증을 경험하는 서비스는 버스트가 완료된 후에도 오랫동안 콜드 익명 힙 페이지를 보관할 수 있습니다.
보정된 <memory-budget> 선언 (memory.high 소프트 한도, 일반적으로 30MB~190MB)을 시스템 앱에 적용하면 두 가지 주요 이점이 있습니다.
- 사전 대응적, 현지화된 페이지 회수: 시스템 프로세스가 구성된
memory.high예산을 초과하면 Linux 커널은 페이지 회수를 해당 cgroup으로 구체적으로 전달하여 콜드 파일 지원 페이지를 먼저 제거하고 전역 기기 메모리 부족을 기다리지 않고 비활성 익명 페이지를 ZRAM으로 압축합니다. - 포그라운드 앱 및 캐시된 앱 풀에 더 많은 RAM 사용 가능: 상시 사용 설정된 시스템 서비스 전반에서 콜드 파일 캐시를 제한하면 기기 전반에서 수십에서 수백 메가바이트의 물리적 RAM이 회수되어 UI 버벅거림이 줄어들고 카메라 실행 압박이 낮아지며 캐시된 상태에서 더 많은 사용자 앱이 활성 상태로 유지됩니다.
시스템 앱 메모리 예산 구성
시스템 앱은 일반 앱과 동일한 <memory-budget> 매니페스트 문법, 프로세스 상태 연속, 디스플레이 밀도 조정, 마지막 일치 절 해결 규칙을 사용합니다. 전체 속성 목록은 XML 속성 참조를 확인하세요.
OEM은 시스템 앱의 AndroidManifest.xml 내에서 직접 <memory-budget> 요소를 선언하거나 Android.bp의 additional_manifests 속성을 사용하여 별도의 XML 파일에서 병합할 수 있습니다. 관례에 따라 AOSP는 참조 시스템 앱 예산을 독립형 *-budget.xml 파일 (핵심 플랫폼 패키지의 경우 frameworks/base/core/memory_budgets/ 또는 packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/, packages/services/Telephony/와 같은 개별 모듈과 함께)에 저장하고 additional_manifests를 사용하여 연결합니다.
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
패키지 수준 (UID)과 프로세스 수준 (PID) 범위 지정
패키지 수준 기준 예산(<application>)을 선언하는지 또는 다중 프로세스 예산(<processes><process>)을 선언하는지에 따라 커널이 UID cgroup 수준(/sys/fs/cgroup/uid_<uid>/memory.high)에서 memory.high를 적용하는지 또는 PID별 cgroup 수준(/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high)에서 적용하는지가 결정됩니다(그림 4 참고).
- 패키지 수준 (
<application>) 예산: APK가 자체 전용 UID (예:Bluetooth,Nfc,NetworkStack,CalendarProvider,Traceur,AppSearch)에서 단일 프로세스를 실행하는 경우에 사용합니다. - 프로세스 수준 (
<processes><process>) 예산: APK가 다른 시스템 패키지와android:sharedUserId(예: UID1001가 있는android.uid.phone또는 UID1000가 있는android.uid.system)를 공유하여 해당 UID를 공유하는 다른 프로세스가 함께 제한되지 않도록 하거나 단일 APK가 고유한 메모리 역할을 가진 여러 프로세스 (예: 백그라운드MediaProvider데몬 및:PhotoPickerUI 하위 프로세스)를 실행하는 경우에 사용합니다.
그림 4. UID 수준 메모리 상한과 PID 수준 메모리 상한 비교
다음 예에서는 RAM 등급 예산을 기본 MediaProvider 프로세스 (<process>)로, 디스플레이 조정 예산을 :PhotoPicker UI 프로세스로 범위 지정하는 MediaProvider-budget.xml를 보여줍니다. 각 android:maxDeviceRamMb 속성은 총 실제 RAM (MemTotal)이 지정된 MB 임곗값 이하인 기기와 일치합니다 (또는 생략된 경우 모든 RAM 용량에 적용됨). 절은 제약이 없는 기본값부터 가장 작은 maxDeviceRamMb 기준점까지 순서대로 정렬되므로 마지막 일치 절 해결 규칙은 가장 엄격한 적용 가능한 등급을 선택합니다.
<?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>
Google Pixel 기기에서 AOSP 참조 예산이 보정된 방식
AOSP의 참조 시스템 앱 메모리 예산은 6GB~16GB의 실제 RAM (6GB, 8GB, 12GB, 16GB 휴대전화, 폴더블, 태블릿 포함)과 약 1.2MP (720x1600)~약 4.5MP (2076x2152)의 디스플레이 해상도를 아우르는 Google Pixel 기기 전반에서 보정되었습니다.
보정은 두 가지 상호 보완적인 데이터 소스, 즉 전체 기기 cgroup v2 필드 원격 분석과 중요한 사용자 여정 (CUJ) 전반에 걸친 제어된 실제 기기 memory.high 스윕을 결합했습니다.
Fleet 전반의 cgroup v2 원격 분석
정기적인 cgroup v2 스냅샷은 기기 전체에서 수집되었으며 실제 RAM 등급 (8GB 이하, 12GB, 16GB 이상)과 프로세스 상태 버킷 (foreground, perceptible, background, cached)으로 분할되었습니다. 통계 분석은 다음 6가지 원칙을 따랐습니다.
memory.current를 작업 세트와 회수 가능한 파일 캐시로 분해:memory.high는 상주 cgroup 요금(memory.current)을 제어하므로 각 스냅샷은memory.stat를 사용하여 익명 힙(anon), 스왑된 익명 페이지(swap), 파일 지원 페이지 캐시(file)로 분해되었습니다(그림 5 참고). 압축 불가능한 총 익명 수요 (P99에서anon + swap)는 프로세스가 ZRAM 소진 위험에 처하지 않도록 예산의 최소 작업 세트 하한을 설정하는 반면,memory.currentP99와anon + swapP99 사이의 큰 격차는 캐시가 매핑되지 않고 콜드 상태인 경우memory.high가 회수할 수 있는 파일 페이지 캐시를 나타냅니다.memory.high에서 회수할 수 없는 메모리 고려: 모든 파일 및 공유 메모리가 콜드 캐시는 아닙니다. 잠긴 페이지 (memory.stat의unevictable, 예:mlock로 잠긴 공유 메모리 또는 호스트 프로세스가 앱의 cgroup에 청구되는 가상 머신의 잠긴 게스트 RAM)는 예산에 하한선을 설정하고 매핑된 페이지 (file_mapped)는 활성 작업 집합의 일부인 경우가 많습니다. 파일 메모리를 회수 가능한 것으로 처리하기 전에unevictable,shmem,file_mapped를 확인합니다. 예를 들어AppSearch메모리의 약 100MB는 잠겨 있고 제거할 수 없으므로 예산은 이 하한선과 핫 호스트 작업 세트를 포함하며 잠긴 영역이 증가할 수 있는 여유 공간이 있습니다.- 유휴 상태 희석 필터링: 콘텐츠 제공업체 (
CalendarProvider) 및 주문형 개발자 도구 (Traceur)는 필드 스냅샷의 70% ~99.9% 를 약 1MB~3MB의 유휴 상태로 보냅니다. 모든 스냅샷에서 원시 백분위수를 계산하면 P95 및 P99 추정치가 크게 희석됩니다. 대신 백분위수와 신뢰 구간은 희석되지 않은 활성 스냅샷(CACHED스냅샷 및 유휴 기준 스냅샷 필터링)을 통해 계산되었습니다. - 기기 클러스터링 신뢰 구간 사용: 단일 기기가 시간이 지남에 따라 많은 주기적 스냅샷을 제공하므로 스냅샷을 독립적인 샘플로 처리하면 꼬리 불확실성이 과소평가됩니다. 모든 P95 및 P99 작업 세트 추정치는 보수적인 기기 클러스터링 95% 신뢰 구간 (고유 기기 수
N_dev를 유효 샘플 크기로 사용)을 사용하여 평가되었습니다. memory.peak를 사용하여 일시적인 버스트 고려: 주기적인 샘플링은 수명이 짧고 할당량이 많은 버스트 (예:NetworkStack종속 포털 TLS 유효성 검사 및 동시 DNS 프로브 또는BluetoothA2DP 및 BLE 초기화)를 놓칠 수 있습니다. 버스트 서비스의 경우 RAM 등급 전반에서 수명 커널memory.peak상위 워터마크의 90~95번째 백분위수를 포함하도록 예산이 조정되었습니다.- RAM 등급이 기기 세대가 아닌 RAM을 반영하는지 확인: 최신 기기는 RAM과 기능이 모두 더 많은 경향이 있으므로 RAM 등급에 따라 증가하는 작업 세트가 실제로는 기능에 따라 증가할 수 있습니다.
maxDeviceRamMb등급을 추가하기 전에 기기 모델별로 데이터를 슬라이스합니다. 예를 들어com.android.phone는 동일한 세대의 12GB 및 16GB 휴대전화에서 동일한 P99anon + swap(~85~90MB)에 도달하는 반면, 이전 12GB 폴더블은 40MB를 사용합니다. 따라서 예산은 RAM 등급이 없는 단일 값을 사용하므로 최신 전화 통신 기능을 제공하는 RAM 부족 기기에 예산이 부족하지 않습니다.
그림 5. 작업 세트와 회수 가능한 파일 캐시 비교
실제 기기 CUJ 및 디스플레이 확장 스윕
원격 분석에서 파생된 예산을 검증하고 디스플레이 비례 예산 (예: :PhotoPicker)과 상태별 백그라운드 예산 (예: Traceur)을 보정하기 위해 6GB~16GB RAM의 실제 기기에서 실시간 memory.high 스윕을 실행했습니다.
- 제로 리폴트 니 찾기: 엔드 투 엔드 CUJ (예: 빠른 사진 그리드 플링, 앨범 탐색, 전체 화면 고해상도 사진 및 동영상 미리보기 스와이프, 트레이스 기록 및 저장)를 실행하는 동안 cgroup
memory.stat카운터(workingset_refault_anon,workingset_refault_file,pgscan,pgsteal)와memory.events(high)를 측정하면서memory.high를 아래로 내렸습니다. 그림 6에 나와 있는 것처럼 최적의 예산은 제로 리폴트 니 바로 위에 있습니다. 제로 리폴트 니는 커널이 활성 작업 세트 스래싱(workingset_refault == 0)을 트리거하지 않고 콜드 일회성 파일 캐시(pgsteal > 0)를 회수하는 기준점입니다. - 해상도 전반의 디스플레이 픽셀 확장 유효성 검사: 약 1.2MP (720×1600)에서 약 4.5MP (2076×2152 펼친 내부 디스플레이)까지 4가지 실제 및 에뮬레이션된 디스플레이 해상도에서
:PhotoPicker를 테스트한 결과 활성 Glide 및 ViewPager2 비트맵 작업 세트가 디스플레이 픽셀 수에 따라 선형적으로 확장되는 것으로 확인되었습니다. 디스플레이가 아닌 기준도 기기에 따라 다릅니다. 픽셀 수가 거의 동일한 경우 16GB 폴더블의 외부 디스플레이에서는 제로 리폴트 니가 약 140MB인 반면 6GB 휴대전화에서는 110~125MB입니다. 85MB 기준에 디스플레이 픽셀당 25바이트 (android:maxMb="85"및android:additionalBytesPerDisplayPixel="25")를 더한 값은 테스트된 모든 디스플레이에서 측정된 무릎 위를 유지합니다 (HD+에서 약 110MB, FHD+에서 약 145MB, 펼쳐진 디스플레이에서 약 190MB). - 백그라운드 상태에서 ART 가비지 컬렉션 확인: cgroup v2에서 Zygote 공유
boot.art및 프레임워크 이미지 페이지는 Zygote의 cgroup에 청구된 상태로 유지되며 ART 동시 마크 컴팩트 (CMC) 가비지 컬렉션 중에 면역 공간으로 처리됩니다. 스트레스 테스트를 통해 시스템 UI 앱이background로 전환될 때memory.high를 강화하면 (예:Traceur에서 30MB로) GC 스래싱이나 스왑 활동을 일으키지 않고 비공개 힙과 페이지 캐시를 깔끔하게 제한하는 것으로 확인되었습니다.
그림 6. CUJ 중에 제로 리폴트 니를 찾습니다.
AOSP 참조 시스템 앱 예산 요약
다음 표에는 RAM 등급 및 프로세스 상태 전반에 걸쳐 AOSP에서 보정된 참조 예산이 요약되어 있습니다.
| 시스템 패키지 또는 프로세스 | 범위 | 8GB 이하 RAM | 12GB RAM | ≥16GB RAM | 기본 보정 드라이버 |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80MB | 95MB | 110MB | P90 전체 기간 memory.peak 버스트 (~75~110MB) 및 2배 이상의 P99 활성 memory.current를 포함합니다. |
com.android.nfc |
UID (<application>) |
50MB | 60MB | 60MB | 콜드 파일 캐시를 자르는 동안 활성 호스트 카드 에뮬레이션 P99 (최대 약 50MB) 및 P90~P95 memory.peak를 처리합니다. |
com.android.providers.calendar |
UID (<application>) |
70MB 기본값 (포그라운드) 50MB perceptible 및 background |
활성 작업 세트는 RAM 등급에 따라 달라지지 않습니다. 백그라운드 예산은 동기화 중에 SQLite 페이지 캐시를 자르면서 리마인더 알람을 위한 익명의 여유 공간을 유지합니다. | ||
com.android.traceur |
UID (<application>) |
85MB 기본값 (포그라운드 및 인식 가능) 30MB background |
활성 트레이스 기록 및 내보내기 (TraceService) 중에 전체 85MB 여유 공간을 유지하면서 유휴 및 브로드캐스트 상태를 30MB로 제한합니다. |
||
com.android.providers.media.module (기본) |
PID (<process>) |
60MB | 75MB | 85MB | 콜드 SQLite 및 썸네일 파일 캐시를 회수하면서 P99 anon + swap보다 약 10~30MB의 여유 공간을 제공합니다. |
com.android.providers.media.module:PhotoPicker |
PID (<process>) |
85MB + 픽셀당 25바이트 (HD+에서 ~110MB, FHD+에서 ~145MB, ~4.5MP 폴드에서 ~190MB) |
Glide 비트맵 작업 세트 무릎과 일치하도록 디스플레이 픽셀 수 (additionalBytesPerDisplayPixel="25")에 비례하여 확장됩니다. |
||
com.android.networkstack |
UID (<application>) |
65MB | 70MB | 85MB | 주로 익명 메모리이며, 종속 포털 TLS, DNS, DHCP 버스트를 처리하기 위해 P95 수명 memory.peak에 맞게 크기가 조정됩니다. |
com.android.appsearch.apk |
UID (<application>) |
130MB | 잠겨 있고 제거할 수 없는 메모리 약 100MB와 핫 호스트 작업 세트 약 5MB를 포함하며, 잠긴 영역이 확장될 수 있는 여유 공간이 있습니다. 콜드 페이지 캐시 (P99에서 최대 약 330MB)가 회수되는 동안 125MB 이상에서 측정된 리폴트가 0입니다. | ||
com.android.phone(TeleService) |
PID (<process>) |
120MB | 공유 UID 1001에서 PID별로 범위가 지정됩니다. 작업 세트는 RAM이 아닌 기기 세대를 추적하므로 단일 예산은 모델별 최고 P99 anon + swap보다 약 30MB 높습니다. |
||
OEM이 자체 기기의 시스템 앱 예산을 재보정하는 방법
시스템 앱을 맞춤설정하거나 (예: 공급업체 블루투스 프로필, 맞춤 전화 통신 확장 프로그램 또는 OEM 미디어 색인 생성기 추가) 추가 사전 설치 시스템 서비스를 제공하는 OEM은 기존 예산을 재보정하거나 하드웨어 포트폴리오의 새 *-budget.xml 파일을 작성할 수 있습니다.
활성 cgroup v2 메모리 프로필 수집
각 타겟 시스템 프로세스에서 대표 워크로드 및 RAM 등급 전반에 걸쳐 /sys/fs/cgroup/uid_<uid>/pid_<pid>/ 아래의 cgroup v2 메모리 노드를 읽습니다.
memory.current: cgroup에 청구된 현재 상주 메모리 (Zygote 공유 페이지 제외)memory.peak: 프로세스 시작 이후 전체 기간 최고memory.current상한선memory.swap.current: ZRAM의 현재 압축 스왑 사용량memory.stat:anon,file,pgscan,pgsteal,workingset_refault_anon,workingset_refault_file분석memory.events:high위반 (memory.high제한 및 회수 이벤트)의 누적 수
dogfood 또는 현장 기기에서 원격 분석을 집계할 때 활성 프로세스 상태 (TOP, FOREGROUND_SERVICE, 활성 바인딩, 활성 동기화)를 유휴 CACHED 또는 유휴 PERSISTENT 상태와 분리하여 유휴 스냅샷이 P95 및 P99 작업 세트 계산을 하향 조정하지 않도록 합니다.
압축 불가능한 힙을 회수 가능한 파일 캐시에서 분리
anon + swap의 99번째 백분위수를 memory.current의 99번째 백분위수와 비교합니다.
- 파일 캐시가 지배적인 서비스 (
file≫anon, 예: 데이터베이스 제공업체, 검색 색인 생성기, 로그 데몬): P99anon + swap에 대해 편안한 여유 공간 (일반적으로 1.5배~4배)과 핫 파일 페이지의 작업 기간을 제공하도록android:maxMb를 설정합니다. 이 구성을 사용하면 커널이 익명 힙을 건드리지 않고도 매핑되지 않은 콜드 파일 페이지를 지속적으로 제거할 수 있습니다.unevictable또는file_mapped가 큰 경우 (잠긴 메모리 또는 메모리 매핑 데이터베이스와 마찬가지로) 이 메모리 사용량도 작업 세트로 계산합니다. - 익명 힙이 지배적이거나 버스트가 발생하는 서비스 (
anon가memory.current의 대부분을 차지함, 예: 네트워킹 또는 연결 스택):memory.peak의 P90~P95를 포함하도록android:maxMb의 크기를 조정하여 일시적인 버스트가 지연 시간에 민감한 스레드에서 동기식 ZRAM 스왑 아웃을 트리거하지 않도록 합니다. - 디스플레이 집약적 UI 프로세스 (예: 선택 도구, 갤러리 또는 시스템 UI 도우미): 최저 및 최고 해상도 디스플레이에서 메가픽셀당 기준 비 UI 공간과 증분 비트맵 공간을 측정하고
android:maxMb및android:additionalBytesPerDisplayPixel를 사용하여 예산을 표현합니다.
실제 기기에서 memory.high 스윕
빌드에 새 예산을 커밋하기 전에 am memory-limiter manual를 사용하거나 userdebug 빌드에서 프로세스의 memory.high cgroup 노드에 직접 작성하여 자동 CUJ 테스트를 실행하는 동안 후보 한도를 테스트합니다.
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MBCUJ 전후에 memory.stat 및 memory.events를 모니터링합니다.
- 예산이 너무 낮음: CUJ 중에
workingset_refault_anon또는workingset_refault_file가 급격히 증가하거나, 활성 포그라운드 작업 중에memory.swap.current가 지속적으로 증가하거나, CUJ 프레임 또는 Binder 지연 시간이 회귀합니다. 기본 변곡점을 초과하여 예산을 늘립니다. - 예산이 너무 높음: 대규모 파일 스트리밍 또는 색인 생성 워크로드 후에도
memory.events:high가0로 유지되고memory.stat의file가 콜드 페이지로 무제한 증가합니다. 활성 작업 집합에 대한 예산을 줄입니다. - 예산이 잘 조정됨: 정상 작동 중에는 안정 상태
memory.current가memory.high미만으로 유지됩니다. 파일 또는 캐시 버스트가 심한 경우pgsteal는 콜드 파일 페이지를 회수하는 반면workingset_refault_anon는 거의 0으로 유지되고 CUJ 지연 시간은 변경되지 않습니다.