메모리 관리 권장사항

이 문서에서는 앱의 메모리 관리와 같은 메모리 관리에 관한 Android 앱용 권장사항 가이드를 따랐다고 가정합니다.

소개

메모리 누수는 컴퓨터 프로그램에서 더 이상 필요하지 않은 할당된 메모리를 해제하지 않을 때 발생하는 리소스 유출의 한 유형입니다. 누수가 발생하면 애플리케이션에서 OS에 사용 가능한 것보다 많은 메모리를 요청하게 되고 이에 따라 애플리케이션이 비정상 종료될 수 있습니다. 리소스를 제대로 폐기하지 않거나 더 이상 필요하지 않은 리스너의 등록을 취소하지 않는 등 여러 가지 부적절한 사용 방식으로 인해 Android 앱에서 메모리 누수가 발생할 수 있습니다.

이 문서에서는 코드에서 메모리 누수를 방지, 감지 및 해결하는 데 도움이 되는 권장사항을 제공합니다. 이 문서의 방법을 시도한 후 SDK에서 메모리 누수가 의심되면 Google SDK 관련 문제를 신고하는 방법을 참고하세요.

지원팀에 문의하기 전에

Google 지원팀에 메모리 누수를 신고하기 전에 권장사항 및 이 문서에 제공된 디버깅 단계에 따라 코드에 오류가 없는지 확인하세요. 이 단계를 통해 문제가 해결될 수 있지만, 해결되지 않을 경우에도 Google 지원팀에서 도움을 제공하기 위해 필요한 정보가 생성됩니다.

메모리 누수 방지하기

Google SDK를 사용하는 코드에서 메모리 누수의 가장 일반적인 원인을 피하려면 다음 권장사항을 따르세요.

Android 앱용 권장사항

Android 애플리케이션에서 다음 작업을 모두 완료했는지 확인하세요.

  1. 사용되지 않는 리소스 해제
  2. 더 이상 필요하지 않은 리스너 등록 취소
  3. 필요하지 않은 작업 취소하기
  4. 수명 주기 메서드를 전달하여 리소스 해제
  5. SDK의 최신 버전 사용

각 권장사항의 구체적인 세부정보는 다음 섹션을 참고하세요.

사용되지 않는 리소스 해제하기

Android 앱에서 리소스를 사용하는 경우, 더 이상 필요하지 않은 리소스를 해제해야 합니다. 해제하지 않으면, 애플리케이션에서 사용을 완료한 후에도 리소스가 메모리를 차지합니다. 자세한 내용은 Android 문서의 활동 수명 주기를 참고하세요.

GeoSDK에서 오래된 GoogleMap 참조 해제하기

일반적인 실수는 NavigationView 또는 MapView를 사용하여 캐시된 경우 GoogleMap에서 메모리 누수가 발생할 수 있는 것입니다. GoogleMap은 GoogleMap을 가져온 NavigationView 또는 MapView와 1:1 관계가 있습니다. GoogleMap이 캐시되지 않도록 하거나 NavigationView#onDestroy 또는 MapView#onDestroy가 호출될 때 참조가 해제되도록 해야 합니다. NavigationSupportFragment, MapSupportFragment 또는 이러한 뷰를 래핑하는 자체 프래그먼트를 사용하는 경우 참조가 Fragment#onDestroyView에서 해제되어야 합니다.

class NavFragment : SupportNavigationFragment() {

  var googleMap: GoogleMap?

  override fun onCreateView(
    inflater: LayoutInflater,
    parent: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View  {
    super.onCreateView(inflater,parent,savedInstanceState)
    getMapAsync{map -> googleMap = map}
  }

  override fun onDestroyView() {
    googleMap = null
  }
}

더 이상 필요하지 않은 리스너 등록 취소하기

Android 앱에서 버튼 클릭 또는 조회 상태 변경과 같은 이벤트의 리스너를 등록하는 경우, 애플리케이션에서 더 이상 이벤트를 모니터링할 필요가 없으면 리스너를 등록 취소해야 합니다. 해제하지 않으면, 애플리케이션에서 사용을 완료한 후에도 리스너가 계속해서 메모리를 차지합니다.

예를 들어 애플리케이션에서 Navigation SDK를 사용하고 다음 리스너를 호출하여 도착 이벤트를 수신 대기한다고 가정합니다(addArrivalListener 메서드를 호출하여 도착 이벤트를 수신 대기). 더 이상 도착 이벤트를 모니터링할 필요가 없는 경우 removeArrivalListener도 호출합니다.

var arrivalListener: Navigator.ArrivalListener? = null

fun registerNavigationListeners() {
  arrivalListener =
    Navigator.ArrivalListener {
      ...
    }
  navigator.addArrivalListener(arrivalListener)
}

override fun onDestroy() {
  navView.onDestroy()
  if (arrivalListener != null) {
    navigator.removeArrivalListener(arrivalListener)
  }

  ...
  super.onDestroy()
}

필요하지 않은 작업 취소하기

Android 앱에서 다운로드 또는 네트워크 요청과 같은 비동기 작업을 시작하는 경우, 작업이 완료되면 작업을 취소해야 합니다. 작업이 취소되지 않으면 앱에서 완료한 후에도 작업이 백그라운드에서 계속 실행됩니다.

권장사항에 대한 자세한 내용은 Android 문서의 앱의 메모리 관리를 참고하세요.

수명 주기 메서드를 전달하여 리소스 해제하기

앱에서 Navigation SDK 또는 Maps SDK를 사용하는 경우 수명 주기 메서드(굵게 표시됨)를 navView에 전달하여 리소스를 해제해야 합니다. Navigation SDK의 NavigationView, Maps SDK 또는 Navigation SDK의 MapView를 사용하여 리소스를 해제할 수 있습니다. NavigationViewMapView를 직접 사용하는 대신 각각 SupportNavigationFragment 또는 SupportMapFragment를 사용할 수도 있습니다. 지원 프래그먼트가 수명 주기 메서드의 전달을 처리합니다.

class NavViewActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    navView = ...
    navView.onCreate(savedInstanceState)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    navView.onSaveInstanceState(savedInstanceState)
  }

  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    navView.onTrimMemory(level)
  }

  /* Same with
    override fun onStart()
    override fun onResume()
    override fun onPause()
    override fun onConfigurationChanged(...)
    override fun onStop()
    override fun onDestroy()
  */
}

SDK의 최신 버전 사용하기

Google SDK는 새로운 기능, 버그 수정 및 성능 개선사항을 반영하여 지속적으로 업데이트되고 있습니다. 이러한 업데이트를 받을 수 있도록 앱의 SDK를 최신 상태로 유지하세요.

메모리 누수 디버그하기

이 문서 앞부분의 관련된 추천을 모두 구현한 후에도 메모리 누수가 있으면, 다음 절차에 따라 디버그하세요.

시작하기 전에 Android에서 메모리를 관리하는 방법을 숙지해야 합니다. 자세한 내용은 메모리 관리의 개요를 참고하세요.

메모리 누수를 디버그하려면 다음 프로세스를 따르세요.

  1. 문제를 재현합니다. 이 단계는 문제를 디버그하는 데 필수적입니다.
  2. 메모리 사용이 예상되는지 확인합니다. 누수인 것으로 보이는 증가된 사용량이 실제로 애플리케이션을 실행하기 위해 필요한 메모리가 아닌지 확인합니다.
  3. 대략적인 수준에서 디버그합니다. 디버그하는 데 사용할 수 있는 여러 유틸리티가 있습니다. Android Studio, Perfetto, Android 디버그 브리지(adb) 명령줄 인터페이스 등 세 가지 표준 도구 세트가 Android에서 메모리 문제를 디버그하는 데 도움이 됩니다.
  4. 앱의 메모리 사용량을 확인합니다. 힙 덤프 및 할당 추적을 가져온 다음 분석합니다.
  5. 메모리 누수를 해결합니다.

다음 섹션에서 이 단계를 자세히 설명합니다.

1단계: 문제 재현

문제를 재현할 수 없는 경우 먼저 메모리 누수를 일으킬 수 있는 시나리오를 고려해 보세요. 문제가 재현되었다는 것을 알면 곧 바로 힙 덤프를 확인하면 됩니다. 하지만 앱 시작 또는 임의의 시점에 힙 덤프를 가져오면 누수를 트리거하기 위한 조건이 활성화되지 않을 수도 있습니다. 문제를 재현하려고 할 때 다양한 시나리오를 고려해 보세요.

  • 어떤 기능 세트가 활성화되나요?

  • 구체적으로 어떤 사용자 작업의 시퀀스가 누수를 트리거하나요?

    • 이 시퀀스를 여러 번 반복적으로 활성화하려고 시도했나요?
  • 앱이 어떤 수명 주기 상태를 순환했나요?

    • 서로 다른 수명 주기 상태를 통해 여러 번 반복을 시도했나요?

SDK의 최신 버전에서 문제를 재현할 수 있는지 확인하세요. 이전 버전의 문제가 이미 해결되었을 수 있습니다.

2단계: 앱의 메모리 사용이 예상되는지 확인

모든 기능에 추가 메모리가 필요합니다. 여러 시나리오를 디버그하는 경우 예상되는 사용인지 또는 실제로 메모리 누수인지 여부를 고려하세요. 예를 들어 여러 기능 또는 사용자 작업의 경우 다음과 같은 가능성을 고려하세요.

  • 누수일 가능성이 높음: 여러 번 반복하여 시나리오를 활성화하면 시간이 지남에 따라 메모리 사용량이 증가합니다.

  • 예상되는 메모리 사용일 가능성이 큼: 시나리오가 중단된 후 메모리가 회수됩니다.

  • 예상되는 메모리 사용일 가능성이 있음: 일정 기간 메모리 사용량이 증가한 다음 줄어듭니다. 제한된 캐시 또는 기타 예상되는 메모리 사용 때문일 수 있습니다.

앱 동작이 예상되는 메모리 사용일 가능성이 높은 경우 앱의 메모리를 관리하여 문제를 해결할 수 있습니다. 도움이 필요하면 앱 메모리 관리를 참고하세요.

3단계: 대략적인 수준에서 디버그

메모리 누수를 디버그할 때는 대략적인 수준에서 시작한 다음 가능성을 좁힌 후 자세히 살펴보세요. 먼저 다음 대략적인 수준의 디버깅 도구 중 하나를 사용하여 시간이 지남에 따라 누수가 있는지 확인합니다.

Android 스튜디오 메모리 프로파일러

이 도구는 소비된 메모리의 시각적 히스토그램을 제공합니다. 이 동일한 인터페이스에서 힙 덤프 및 할당 추적도 트리거할 수 있습니다. 이 도구는 기본으로 권장됩니다. 자세한 내용은 Android 스튜디오 메모리 프로파일러를 참고하세요.

Perfetto 메모리 카운터

Perfetto를 사용하면 여러 측정항목을 정밀하게 추적하고 단일 히스토그램에 모두 표시할 수 있습니다. 자세한 내용은 Perfetto 메모리 카운터를 참고하세요.

Perfetto 사용자 인터페이스

Android 디버그 브리지(adb) 명령줄 유틸리티

Perfetto로 추적할 수 있는 측정항목 대부분은 직접 쿼리할 수 있는 adb 명령줄 유틸리티로도 사용할 수 있습니다. 중요한 예:

  • Meminfo를 사용하면 특정 시점의 자세한 메모리 정보를 볼 수 있습니다.

  • Procstats는 시간 경과에 따라 집계된 중요한 통계를 제공합니다.

여기에서 확인해야 할 중요한 통계는 시간 경과에 따라 앱에서 필요로 하는 최대 물리적 메모리 사용량(maxRSS)입니다. MaxPSS는 정확하지 않을 수도 있습니다. 정확도를 높이는 방법은 adb shell dumpsys procstats --help –start-testing 플래그를 참고하세요.

할당 추적

할당 추적은 메모리가 할당되고 해제되지 않은 스택 트레이스를 식별합니다. 이 단계는 특히 네이티브 코드에서 누수를 추적할 때 유용합니다. 이 도구는 스택 트레이스를 식별하므로 신속하게 근본 원인을 디버그하거나 문제를 재현하는 방법을 파악하는 데 매우 유용할 수 있습니다. 할당 추적을 사용하는 방법은 할당 추적을 통해 네이티브 코드로 메모리 디버그하기를 참고하세요.

4단계: 힙 덤프로 앱의 메모리 사용량 확인

메모리 누수를 감지하는 한 가지 방법은 앱의 힙 덤프를 가져온 다음 누수가 있는지 검사하는 것입니다. 힙 덤프는 앱의 메모리에 있는 모든 객체의 개요로, 메모리 누수 및 기타 메모리 관련 문제를 진단하는 데 사용할 수 있습니다.

Android 스튜디오는 GC에서 해결할 수 없는 메모리 누수를 감지할 수 있습니다. 힙 덤프를 캡처할 때 Android 스튜디오에서는 여전히 도달할 수 있지만 이미 소멸된 활동 또는 프래그먼트가 있는지 여부를 확인합니다.

  1. 힙 덤프 캡처
  2. 힙 덤프를 분석하여 메모리 누수 찾기
  3. 메모리 누수 해결

자세한 내용은 다음 섹션을 참고하세요.

힙 덤프 캡처

힙 덤프를 캡처하려면 Android 디버그 브리지(adb) 또는 Android 스튜디오 메모리 프로파일러를 사용하세요.

adb를 사용하여 힙 덤프 캡처하기

adb를 사용하여 힙 덤프를 캡처하려면 다음 단계를 따르세요.

  1. Android 기기를 컴퓨터에 연결합니다.
  2. 명령어 프롬프트를 열고 adb 도구 있는 디렉터리로 이동합니다.
  3. 힙 덤프를 캡처하려면 다음 명령어를 실행하세요.

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

  4. 힙 덤프를 가져오려면 다음 명령어를 실행하세요.

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

Android 스튜디오를 사용하여 힙 덤프 캡처하기

Android 스튜디오 메모리 프로파일러를 사용하여 힙 덤프를 캡처하려면 Android 힙 덤프 캡처 섹션 에서 다음 단계를 따르세요.

힙 덤프를 분석하여 메모리 누수 찾기

힙 덤프를 캡처한 후 Android 스튜디오 메모리 프로파일러를 사용하여 분석할 수 있습니다. 힙 덤프를 분석하려면 다음 단계를 따르세요.

  1. Android 스튜디오에서 Android 프로젝트를 엽니다.

  2. 실행을 선택한 다음 구성 디버그를 선택합니다.

  3. Android 프로파일러 탭을 엽니다.

  4. 메모리를 선택합니다.

  5. 힙 덤프 열기를 선택하고 생성한 힙 덤프 파일을 선택합니다. 메모리 프로파일러가 앱의 메모리 사용량 그래프를 표시합니다.

  6. 그래프를 사용하여 힙 덤프를 분석합니다.

    • 더 이상 사용되지 않는 객체를 식별합니다.

    • 많은 메모리를 사용하는 객체를 식별합니다.

    • 각 객체가 사용 중인 메모리의 양을 확인합니다.

  7. 이 정보를 사용하여 범위를 좁히거나 메모리 누수의 원인을 찾아 해결합니다.

5단계: 메모리 누수 해결

메모리 누수의 원인을 식별한 후 메모리 누수를 해결할 수 있습니다. Android 앱에서 메모리 누수를 해결하면 앱의 성능과 안정성을 개선하는 데 도움이 됩니다. 시나리오에 따라 세부정보는 다를 수 있습니다. 하지만 다음과 같은 추천이 도움이 될 수 있습니다.

  • 앱에서 Android 주제 앱의 메모리 관리에서 권장하는 대로 메모리를 할당하고 할당 해제하는지 확인합니다.

  • 사용되지 않는 코드나 리소스를 앱에서 삭제합니다. Android 앱에 대한 자세한 내용은 Android 앱용 권장사항을 참고하세요.

기타 디버깅 도구

이 단계가 완료된 후에도 문제를 찾고 해결한 경우 다음 도구를 사용해 보세요.

할당 추적을 통해 네이티브 코드로 메모리 디버그하기

네이티브 코드를 직접 사용하지 않더라도 Google SDK를 포함하여 여러 일반 Android 라이브러리에서는 사용합니다. 네이티브 코드에 메모리 누수가 있다고 생각되면 여러 도구를 사용하여 디버그할 수 있습니다. Android 스튜디오 또는 heapprofd(Perfetto와도 호환됨)를 통한 할당 추적은 메모리 누수의 잠재적인 원인을 파악할 수 있는 좋은 방법이며 가장 빠른 디버그 방법입니다.

할당 추적에는 또한 힙에서 찾을 수 있는 민감한 정보를 포함하지 않고 결과를 공유할 수 있는 뚜렷한 장점이 있습니다.

LeakCanary로 누수 식별하기

LeakCanary는 Android 앱에서 메모리 누수를 식별하기 위한 강력한 도구입니다. 앱에서 LeakCanary를 사용하는 방법에 대해 자세히 알아보려면 LeakCanary를 방문하세요.

Google SDK 관련 문제를 신고하는 방법

이 문서에 제시된 방법을 시도한 후에도 Google SDK의 메모리 누수가 의심스러우면, 아래와 같은 정보를 최대한 많이 확보하여 고객지원팀에 문의하세요.

  • 메모리 누수를 재현할 수 있는 단계. 단계에 복잡한 코딩이 필요한 경우, 문제를 샘플 앱에 복제하는 코드를 복사하고 UI에서 누수를 트리거하기 위해 실행해야 하는 추가 단계를 제공하면 도움이 될 수도 있습니다.

  • 문제를 재현한 앱에서 캡처된 힙 덤프. 메모리 사용량이 상당히 증가했음을 보여주는 서로 다른 두 시점에서 힙 덤프를 캡처합니다.

  • 기본 메모리 누수가 예상되면 heapprofd의 할당 추적 출력을 공유합니다.

  • 누수 조건을 재현한 후 작성된 버그 신고

  • 메모리 관련 비정상 종료의 스택 트레이스

    중요 사항: 스택 트레이스는 일반적으로 그 자체만으로는 메모리 문제를 디버그하기에 충분하지 않으므로 다른 형태의 정보 중 하나도 제공해야 합니다.