메모리 관리 정보

메모리 최적화는 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가 자동으로 프로세스를 종료합니다. 이 종료는 메모리 제한기 종료 이유(Android 17, 26Q4부터 사용 가능)에서 ApplicationExitInfo를 사용하여 로깅됩니다.

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

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

경고 및 시정 조치 기준점은 기기의 물리적 RAM 용량과 프로세스 상태에 따라 조정됩니다. 이러한 한도는 두 가지 단계로 적용됩니다.

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

사용자 인식 서비스

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

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

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

R8 요구사항

바이트코드 크기를 최소화하고 기본 Java 프로세스 오버헤드를 줄이기 위해 Google Play Console은 앱 품질 가이드라인의 일부로 코드 최적화를 평가합니다. 게임에 적용되는 최적화 요구사항 및 제한사항에 관한 자세한 내용은 Android vitals 문서의 DEX 코드 최적화를 참고하세요.

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

게임 엔진으로 빌드하는 경우 기본 R8 동작은 엔진 및 버전에 따라 다릅니다.

  • Unity: 코드 축소는 기본적으로 사용 중지되어 있으므로 프로젝트 설정에서 사용 설정해야 합니다. 버전 지원 및 구성 단계에 관한 자세한 내용은 R8을 사용하여 Unity 코드 축소 및 최적화를 참고하세요.
  • Unreal Engine: UE 4.25부터 R8은 출시 빌드에서 기본적으로 사용 설정되므로 JNI 브리지 및 플러그인의 유지 규칙을 관리해야 합니다. 버전 세부정보 및 UPL 구성에 관한 자세한 내용은 R8을 사용하여 언리얼 엔진 코드 축소 및 최적화를 참고하세요.

비트맵 요구사항

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

메모리 사용량 모니터링

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

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

메모리 사용 감소 전략

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

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