메모리 관리 정보

메모리 최적화는 Android에서 안정적인 고성능 게임 환경을 제공하는 데 매우 중요합니다. 이 가이드에서는 메모리 효율성이 중요한 이유, Android 운영체제에서 프로세스 메모리 한도를 관리하는 방법, 게임의 기술적 품질을 모니터링하고 개선하는 데 도움이 되는 Google Play Console의 새로운 메모리 측정항목을 간략하게 설명합니다.

메모리 최적화의 중요성

게임의 메모리를 최적화하는 것은 플레이어 유지율을 유지하고, 기기 호환성을 확대하고, 플랫폼 품질 기준을 준수하는 데 필수적입니다.

  • 콜드 스타트 방지 (사용자 환경 및 유지율): 플레이어가 일시적으로 게임에서 전환하면 (예: 알림에 답장하거나 메시지를 확인하기 위해) 운영체제에서 게임 프로세스를 백그라운드에 배치합니다. 게임의 백그라운드 메모리 사용 공간이 너무 크면 시스템의 로우 메모리 킬러 (LMK)가 포그라운드 작업의 RAM을 확보하기 위해 게임 프로세스 종료를 우선시합니다. 다음에 사용자가 재개할 때 게임은 원활하고 즉각적인 웜 재개 대신 스토리지에서 대용량 그래픽 애셋, 오디오, 게임 엔진 바이너리를 완전히 다시 로드하는 긴 콜드 스타트를 거쳐야 합니다. 백그라운드 메모리 사용량을 낮게 유지하면 이러한 자동 백그라운드 종료를 방지하여 사용자 상태를 보존하고 플레이어가 세션을 즉시 재개할 수 있도록 합니다. 시스템 LMK 동작에 관한 자세한 내용은 Android vitals - 로우 메모리 킬러 가이드를 참고하세요.
  • 생태계 및 기기 안정성: 비효율적인 메모리 사용과 메모리 누수로 인해 전반적인 시스템 상태가 저하됩니다. 시스템 메모리가 부족하면 시스템에 심각한 압력이 가해져 프레임 속도 저하, UI 끊김, 오디오 결함이 발생합니다. 메모리 압력이 너무 심하면 시스템의 로우 메모리 킬러 (LMK)가 백그라운드 프로세스를 적극적으로 종료하여 플레이어가 작업 간에 전환할 때 다른 애플리케이션에서 콜드 스타트가 느려지고 사용자 상태가 손실됩니다.
  • 플랫폼 수준 종료: Android 17 (API 수준 37)부터 시스템은 메모리를 너무 많이 사용하는 프로세스를 종료하는 데 더 적극적입니다. 게임의 사용 공간이 너무 크면 OS에서 표준 스택 트레이스를 생성하지 않고 프로세스를 갑자기 종료할 수 있습니다.
  • 기기 호환성: 플래그십 기기는 12GB~16GB의 RAM을 제공하지만 전 세계 게임 사용자의 상당 부분이 4GB 또는 6GB의 RAM을 사용하는 기기를 사용합니다. 적절한 메모리 관리를 통해 복잡하고 별도의 애셋 패키지 없이 모든 하드웨어 등급에서 게임에 액세스하고 게임이 응답성을 유지할 수 있습니다.

Android의 메모리 이해

효과적인 메모리 예산 책정 전략을 설계하려면 개발자는 Android 플랫폼에서 실제 메모리를 관리하는 방법과 게임의 활성 사용 공간을 측정하는 방법을 이해해야 합니다.

핵심 Android 메모리 개념

플랫폼 수준 메모리 관리에 관한 기본 개념은 공식 메모리 관리 개요 문서를 참고하세요. 이 리소스에서는 다음 네 가지 아키텍처 영역을 다룹니다.

  • 메모리 개요: Android는 페이징과 메모리 매핑 (mmap)을 사용하여 RAM을 관리합니다. 디스크의 기존 스왑 파일을 지원하지 않습니다. 대신 페이지 압축 (zRAM 사용)과 페이지 회수를 통해 실제 메모리를 확보합니다.
  • 프로세스 간 메모리 할당: Android는 전체 시스템에서 RAM을 공유합니다. Dalvik 또는 ART 가상 머신 실행을 위한 특정 힙을 할당하는 동시에 네이티브 개발 환경 (예: C++ 게임 엔진)에서 네이티브 시스템 힙의 메모리를 요청할 수 있도록 합니다.
  • 앱 메모리 관리: 다중 프로세스 모델에서 작동하는 Android는 애플리케이션이 수명 주기 상태를 동적으로 모니터링하고 시스템 상태를 지원하기 위해 캐시되지 않은 그래픽 및 비트맵과 같은 불필요한 리소스를 자발적으로 해제할 것으로 예상합니다.
  • 프로세스 및 스레드 개요: 시스템은 현재 사용자 인식 가시성과 중요도를 기준으로 프로세스를 계층 구조로 분류하여 메모리가 부족한 상황에서 어떤 프로세스를 계속 실행하고 어떤 프로세스를 먼저 종료할지 결정합니다.

총 메모리 사용 공간 측정항목

플랫폼 수준 Android 17 메모리 제한기는 총 상주 크기 (RSS) 또는 가상 메모리 크기 대신 총 메모리 사용 공간을 사용하여 프로세스 소비를 평가합니다.

총 메모리 사용 공간 = 익명 RSS (RssAnon) + 압축 해제된 스왑 (VmSwap)

게임이 플랫폼 한도를 초과하지 않도록 하려면 시스템 수준에서 이러한 측정항목이 나타내는 내용을 정확히 이해해야 합니다. 이러한 측정항목, 실제 RAM 할당, 파일 지원 페이지가 처리되는 방법에 관한 자세한 내용은 RSS 및 스왑 측정항목 이해가이드의 메모리 사용량 모니터링을 참고하세요.

메모리 제약조건

시스템 안정성을 유지하고 애플리케이션이 과도한 리소스를 소비하지 않도록 Android 플랫폼은 실행 중인 프로세스의 메모리 한도를 관리합니다.

Android 17 이상의 메모리 제한기

Android 17 (API 수준 37) 이상에서는 Linux cgroup v2를 사용하여 엄격한 앱별 메모리 한도를 관리하여 개별 앱이 시스템 전체의 불안정성을 일으키지 않도록 합니다. 기술 구현에 관한 자세한 내용은 AOSP 메모리 제한기 가이드메모리 효율성 우선순위 지정: Android 17 블로그의 필수 단계를 참고하세요.

  • 메커니즘: 메모리 제한기는 모든 애플리케이션 프로세스를 모니터링하고 프로세스의 수명 주기 상태에 따라 한도를 동적으로 할당합니다.
    • 표시되는 프로세스 (포그라운드): 현재 UI를 표시하는 앱 프로세스는 더 큰 리소스 작업 세트를 실행할 것으로 예상되며 더 관대한 한도가 제공됩니다.
    • 표시되지 않는 프로세스 (백그라운드 또는 서비스): UI를 표시하지 않고 활발하게 작업하는 앱 프로세스는 더 엄격하고 제한적인 예산으로 제한됩니다.
  • 커널 속성: 서비스는 다음 두 가지 기본 속성에 의존합니다.
    • memory.high: 유연한 한도입니다. 초과되면 커널이 프로세스를 제한하고 메모리를 적극적으로 회수하려고 시도합니다. 이 회수로 인해 게임의 성능이 저하될 수 있습니다.
    • memory.swap.max: 프로세스에서 사용할 수 있는 스왑 또는 zRAM 공간의 하드 캡을 관리합니다.
  • 종료 동작: 프로세스가 익명 메모리를 memory.high 초과하여 계속 할당하고 스왑 용량을 소진하면 할당이 실패하고 OS에서 자동으로 프로세스를 종료합니다. 이 종료는 ApplicationExitInfo 메모리 제한기 종료 이유 (Android 17, 26Q4부터 사용 가능)에서 사용하여 로깅됩니다.

Play Console vitals의 새로운 메모리 한도

개발자가 메모리 문제를 사전에 식별할 수 있도록 Google Play에서는 Play Console Android vitals에 새로운 측정항목을 도입합니다. Play Console은 게임 세션의 90번째 백분위수 (P90) 익명 RSS + 스왑 메모리 사용 공간을 추적하여 극단적인 이상점을 식별합니다.

경고 및 시행 기준점은 기기의 실제 RAM 용량과 프로세스 상태를 기준으로 조정됩니다. 이러한 한도는 두 가지 고유한 단계에서 적용됩니다.

자세한 가이드라인과 메모리 한도는 Android vitals - ' 잘못된 동작 기준점은 무엇인가요?를 참고하세요.

사용자 인식 서비스

인식 가능한 서비스는 Android 시스템에서 사용자에게 눈에 띄는 것으로 간주하는 중요한 백그라운드 프로세스입니다. 이 상태에는 실행 중인 모든 프로세스가 포함됩니다.

  • 포그라운드 서비스 (FGS)
  • 신속 처리 작업
  • 사용자가 개시한 데이터 전송 작업
  • 시스템 바인딩 서비스 또는 다른 애플리케이션에 바인딩된 서비스

인식 가능한 서비스는 백그라운드에서 중요한 장기 실행 작업을 위해 설계되었으므로 누적 수명 주기 누수에 매우 취약합니다. Android 플랫폼 메모리 한도는 이 상태를 포그라운드가 아닌 것으로 취급합니다. 즉, 게임이 백그라운드에 있지만 인식 가능한 서비스를 계속 실행하는 경우 Play 고객센터 페이지에 표시된 더 엄격한 백그라운드 또는 서비스 메모리 한도가 적용됩니다. 게임은 포그라운드에서 백그라운드 인식 가능한 서비스 상태로 이동할 때 불필요한 애셋을 적극적으로 트리밍해야 합니다.

R8 요구사항

바이트코드 크기를 최소화하고 기본 Java 프로세스 오버헤드를 줄이기 위해 Google Play Console은 앱 품질 가이드라인의 일부로 코드 최적화를 평가합니다. 빌드 파이프라인 구성에 관한 자세한 내용은 R8로 앱 최적화 사용 설정 가이드를 참고하세요.

프로젝트에서 R8을 구성하려면 R8로 앱 최적화 사용 설정 가이드를 따르세요. 고급 축소 및 최적화 설정을 사용 설정하려면 전체 모드에서 R8 사용을 참고하세요. R8이 클래스를 난독화하거나 데드 코드를 삭제하지 못하도록 하는 규칙을 식별하려면 R8 구성 분석기 사용을 참고하세요.

비트맵 요구사항

비트맵은 최신 고화질 게임에서 메모리 사용량의 상당 부분을 차지합니다. 비트맵 픽셀 데이터는 Android 8.0 (API 수준 26) 이상에서 관리되지 않는 네이티브 힙에 직접 저장되므로 최적화되지 않은 이미지 로드로 인해 프로세스가 플랫폼 메모리 기준점을 초과할 수 있습니다. 이미지 크기 조정 및 캐싱에 관한 권장사항은 이미지 사용 최적화를 참고하세요.

메모리 사용량 모니터링

게임의 메모리를 효과적으로 최적화하려면 먼저 Android 플랫폼에서 사용 공간을 측정하는 방법을 이해해야 합니다. Android 17은 파일 지원 또는 GPU 전용 메모리를 제외하고 익명 RSS (RssAnon)와 압축 해제된 스왑 (VmSwap)의 합계를 추적하도록 메모리 측정항목을 업데이트합니다. 이 가이드에서는 Perfetto 및 meminfo와 같은 시스템 수준 도구를 활용하고, ProfilingManageronTrimMemory와 같은 진단 API를 구현하고, Unity 및 Unreal Engine 내에서 정확한 메모리 할당을 추출하는 방법을 자세히 설명합니다. 게임을 정확하게 프로파일링하고 기존 런타임 메모리 폴링과 관련된 성능 끊김을 방지하는 방법을 알아보세요.

자세한 내용은 메모리 사용량 모니터링을 참고하세요.

메모리 사용 감소 전략

게임 엔진은 크로스 플랫폼 개발을 간소화하지만 기본 메모리 처리로 인해 OS 수준 메모리 한도가 트리거될 수 있습니다. 이 페이지에서는 Unity 및 Unreal Engine에 맞게 특별히 조정된 실용적인 최적화 단계를 자세히 설명합니다. Java 기반 onTrimMemory에 의존하면 Unity에서 교착 상태가 발생하는 이유와 대신 네이티브 수명 주기 콜백을 사용하는 방법을 알아보세요. 또한 ASTC 8x8 텍스처 압축 사용 및 애셋 언로드 구성과 같은 주요 애셋 수준 최적화를 통해 모든 하드웨어 등급에서 게임을 원활하게 실행할 수 있습니다.

자세한 내용은 메모리 사용량 줄이기를 참고하세요.