메모리 프로파일러로 앱의 메모리 사용량 검사

메모리 프로파일러는 끊김 현상, 멈춤, 심지어 앱 비정상 종료로 이어질 수 있는 메모리 누수 및 메모리 변동을 식별하는 데 도움이 되는 Android 프로파일러의 구성요소입니다. 메모리 프로파일러는 앱의 메모리 사용 현황을 실시간 그래프로 보여주며, 메모리 프로파일러를 사용하여 힙 덤프를 캡처하고 가비지 컬렉션을 강제 실행하고 메모리 할당을 추적할 수 있습니다.

메모리 프로파일러를 열려면 다음 단계를 따르세요.

  1. View > Tool Windows > Profiler를 클릭합니다(툴바에서 Profile 을 클릭해도 됩니다).
  2. Android 프로파일러 툴바에서 프로파일링할 기기 및 앱 프로세스를 선택합니다. USB를 통해 기기를 연결했는데 목록에 표시되지 않으면 USB 디버깅을 사용 설정했는지 확인합니다.
  3. MEMORY 타임라인의 아무 곳이나 클릭하여 메모리 프로파일러를 엽니다.

또는 명령줄에서 dumpsys를 사용하여 앱 메모리를 검사할 수 있고 logcat에서 GC 이벤트를 확인할 수도 있습니다.

앱 메모리를 프로파일링해야 하는 이유

Android에서는 관리되는 메모리 환경을 제공합니다. 즉, 앱에서 일부 객체를 더 이상 사용하지 않는다고 판단되면 가비지 컬렉터가 사용되지 않는 메모리를 힙에 돌려보냅니다. Android에서 사용되지 않는 메모리를 찾는 방법은 끊임없이 개선되고 있지만, 모든 Android 버전의 특정 시점에서 시스템이 코드를 일시중지해야 합니다. 대부분의 경우 일시중지를 감지할 수 없습니다. 하지만, 시스템에서 메모리를 수집할 수 있는 것보다 빠르게 앱에서 메모리를 할당하는 경우 컬렉터가 할당하기에 충분한 메모리를 확보하는 동안 앱이 지연될 수 있습니다. 이로 인해 앱이 프레임을 건너뛰고 눈에 띄게 느려질 수 있습니다.

앱이 느려지지 않더라도 메모리 누수가 발생할 경우 앱이 백그라운드에 있는 동안에도 메모리를 유지할 수 있습니다. 이 동작으로 인해 불필요한 가비지 컬렉션 이벤트가 강제로 실행되어 시스템의 나머지 메모리 성능이 저하될 수 있습니다. 결국 시스템에서 메모리를 확보하기 위해 앱 프로세스를 강제 종료해야 합니다. 그러면 사용자가 앱으로 돌아올 때 앱을 완전히 다시 시작해야 합니다.

이 문제를 방지하려면 메모리 프로파일러를 사용하여 다음을 처리해야 합니다.

  • 타임라인에 성능 문제를 일으킬 수 있는 바람직하지 않은 메모리 할당 패턴이 있는지 찾아봅니다.
  • 자바 힙을 덤프하여 특정 시점에서 어느 객체가 메모리를 모두 사용하는지 확인합니다. 장기간 여러 번 힙을 덤프하면 메모리 누수를 식별하는 데 도움이 될 수 있습니다.
  • 정상적인 사용자 상호작용과 극단적인 사용자 상호작용 동안 메모리 할당을 기록하여 코드에서 단기간에 너무 많은 객체를 할당하거나 누출되는 객체를 할당하는 부분을 정확히 파악합니다.

앱의 메모리 사용을 줄일 수 있는 프로그래밍 방법에 관한 정보는 앱 메모리 관리를 참고하세요.

메모리 프로파일러 개요

메모리 프로파일러를 처음 열면 앱의 메모리 사용 현황을 보여주는 상세 타임라인이 표시되고 가비지 컬렉션을 강제 실행하고, 힙 덤프를 캡처하고, 메모리 할당을 기록할 수 있는 도구에 액세스하게 됩니다.

그림 1. 메모리 프로파일러

그림 1에 표시된 것처럼 메모리 프로파일러의 기본 뷰에는 다음 항목이 포함됩니다.

  1. 가비지 컬렉션 이벤트를 강제 실행하는 버튼
  2. 힙 덤프를 캡처하는 버튼

    참고: 메모리 할당을 기록하는 버튼은 Android 7.1(API 수준 25) 이하를 실행하는 기기에 연결된 경우에만 힙 덤프 버튼의 오른쪽에 표시됩니다.

  3. 프로파일러에서 메모리 할당을 캡처하는 빈도를 지정하는 드롭다운 메뉴. 적절한 옵션을 선택하면 프로파일링하는 동안 앱 성능을 개선하는 데 도움이 될 수도 있습니다.
  4. 타임라인을 확대/축소하는 버튼
  5. 라이브 메모리 데이터의 앞으로 건너뛰는 버튼
  6. 액티비티 상태, 사용자 입력 이벤트, 화면 회전 이벤트를 보여주는 이벤트 타임라인
  7. 메모리 사용 타임라인. 다음 항목이 포함됩니다.
    • 메모리 카테고리별 메모리 사용량의 누적 그래프. 메모리 사용량은 왼쪽의 y축과 상단의 색상 키로 표시됩니다.
    • 할당된 객체의 수를 나타내는 파선. 객체 수는 오른쪽의 y축에 표시됩니다.
    • 각 가비지 컬렉션 이벤트의 아이콘

그러나 Android 7.1 이하를 실행하는 기기를 사용하는 경우 기본적으로 모든 프로파일링 데이터가 표시되지는 않습니다. 'Advanced profiling is unavailable for the selected process'라는 메시지가 표시되는 경우 다음 항목을 표시하려면 고급 프로파일링을 사용 설정해야 합니다.

  • 이벤트 타임라인
  • 할당된 개체의 수
  • 가비지 컬렉션 이벤트

Android 8.0 이상에서는 디버그 가능한 앱에 관해 고급 프로파일링이 항상 사용 설정됩니다.

메모리 계산 방법

메모리 프로파일러 상단(그림 2)에는 Android 시스템에 따라 앱에서 커밋한 모든 비공개 메모리 페이지를 기반으로 한 숫자가 표시됩니다. 시스템이나 다른 앱과 공유되는 페이지는 포함되지 않습니다.

그림 2. 메모리 프로파일러 상단의 메모리 카운트 범례

메모리 카운트의 카테고리는 다음과 같습니다.

  • Java: 자바 또는 Kotlin 코드에서 할당된 객체의 메모리.
  • Native: C 또는 C++ 코드에서 할당된 객체의 메모리

    앱에서 C++를 사용하지 않더라도, Android 프레임워크에서 이미지 애셋 및 기타 그래픽을 처리하는 경우와 같이 개발자 대신 다양한 작업을 처리하기 위해 네이티브 메모리를 사용하므로 코드가 자바 또는 Kotlin으로 작성된 경우에도 일부 사용된 네이티브 메모리가 여기에 표시될 수 있습니다.

  • Graphics: GL 표면, GL 텍스처 등 픽셀을 화면에 표시하기 위해 그래픽 버퍼 큐에 사용되는 메모리. 참고: 이 메모리는 전용 GPU 메모리가 아니라 CPU와 공유됩니다.

  • Stack: 앱의 네이티브 및 자바 스택에서 사용하는 메모리. 일반적으로 앱에서 실행 중인 스레드 수와 관련이 있습니다.

  • Code: 앱에서 dex 바이트 코드, 최적화되거나 컴파일된 dex 코드, .so 라이브러리, 글꼴 등 코드 및 리소스에 사용하는 메모리.

  • Others: 앱에서 사용하는 메모리 중 시스템에서 분류하는 방법을 알지 못하는 메모리.

  • Allocated: 앱에서 할당한 자바/Kotlin 객체 수. C 또는 C++에서 할당된 객체는 계산되지 않습니다.

    Android 7.1 이하를 실행하는 기기에 연결된 경우 메모리 프로파일러가 실행 중인 앱에 연결된 때만 이 할당 카운트가 시작됩니다. 따라서 프로파일링을 시작하기 전에 할당된 객체는 모두 고려되지 않습니다. 하지만 Android 8.0 이상의 경우 모든 할당을 추적하는 기기 내 프로파일링 도구가 포함되므로, 이 숫자는 항상 Android 8.0 이상의 앱에서 실행 중인 총 자바 객체 수를 나타냅니다.

새 메모리 프로파일러는 이전 Android Monitor 도구의 메모리 카운트와는 메모리를 다르게 기록하므로 메모리 사용량이 더 많은 것으로 보일 수도 있습니다. 메모리 프로파일러는 추가 카테고리를 모니터링하므로 총 사용량이 증가하지만, 자바 힙 메모리만 중요한 경우에는 'Java' 숫자가 이전 도구의 값과 비슷해야 합니다. 자바 숫자는 아마도 Android Monitor에 표시된 숫자와 정확히 일치하지 않지만, 이 새로운 숫자는 Zygote에서 포크된 이후 앱의 자바 힙에 할당된 모든 실제 메모리 페이지 수를 나타냅니다. 따라서 이 숫자는 앱에서 실제로 사용 중인 실제 메모리의 양을 정확하게 나타냅니다.

메모리 할당 보기

메모리 할당에는 메모리의 각 자바 객체와 JNI 참조가 할당된 방식이 표시됩니다. 구체적으로 객체 할당과 관련한 다음과 같은 정보가 메모리 프로파일러에 표시될 수 있습니다.

  • 할당된 객체의 유형 및 할당된 객체가 사용하는 공간의 크기
  • 각 할당의 스택 트레이스(할당이 들어 있는 스레드 포함)
  • 객체가 할당 해제된 시점(Android 8.0 이상을 실행하는 기기를 사용하는 경우에만).

자바 및 Kotlin 할당을 기록하려면 Record Java / Kotlin allocations를 선택한 다음 Record를 선택합니다. 기기에서 Android 8 이상을 실행 중이면 메모리 프로파일러 UI가 진행 중인 기록을 표시하는 별도의 화면으로 전환됩니다. 기록 위에 있는 미니 타임라인과 상호작용할 수 있습니다(예: 선택 범위 변경). 기록을 완료하려면 Stop 을 선택합니다.

메모리 프로파일러의 자바 할당 시각화

Android 7.1 이하에서는 메모리 프로파일러가 레거시 할당 기록을 사용하여 Stop을 클릭할 때까지 타임라인에 기록을 표시합니다.

타임라인의 영역을 선택하고 나면(또는 Android 7.1 이하를 실행하는 기기를 사용하여 기록 세션을 완료한 경우) 할당된 객체의 목록이 클래스 이름별로 그룹화되고 힙 개수로 정렬되어 표시됩니다.

할당 기록을 검사하려면 다음 단계를 따르세요.

  1. 목록을 둘러보고 힙 개수가 비정상적으로 크고 누출될 가능성이 있는 객체를 찾습니다. 알려진 클래스를 찾으려면 Class Name 열 헤더를 클릭하여 알파벳순으로 정렬합니다. 그런 다음, 클래스 이름을 클릭합니다. 오른쪽에 Instance View 창이 표시되고 그림 3과 같이 이 클래스의 각 인스턴스가 표시됩니다.
    • 또는 Filter 를 클릭하거나 Control+F(Mac의 경우 Command+F)를 누르고 검색 필드에 클래스 또는 패키지 이름을 입력하여 신속하게 객체를 찾을 수 있습니다. 드롭다운 메뉴에서 Arrange by callstack을 선택한 경우 메서드 이름으로 검색할 수도 있습니다. 정규 표현식을 사용하려면 Regex 옆의 체크박스를 선택합니다. 대소문자를 구분하는 검색어의 경우 Match case 옆의 체크박스를 선택합니다.
  2. Instance View 창에서 인스턴스를 클릭합니다. 아래에 Call Stack 탭이 표시되고 인스턴스가 할당된 위치 및 인스턴스가 들어 있는 스레드가 표시됩니다.
  3. Call Stack 탭에서 아무 줄이나 마우스 오른쪽 버튼으로 클릭하고 Jump to Source를 선택하여 편집기에서 코드를 엽니다.

그림 3. 할당된 각 객체에 관한 세부정보가 오른쪽의 Instance View에 표시됨

할당된 객체 목록 위의 두 메뉴를 사용하여 어떤 힙을 검사할지와 데이터를 구성하는 방법을 선택할 수 있습니다.

왼쪽 메뉴에서 검사할 힙을 선택합니다.

  • default heap: 시스템에서 힙을 지정하지 않은 경우.
  • image heap: 부팅하는 동안 미리 로드된 클래스가 포함된 시스템 부팅 이미지. 여기서는 할당이 결코 이동하거나 사라지지 않습니다.
  • zygote heap: Android 시스템에서 앱 프로세스가 포크되는 COW(기록 중 복사) 힙.
  • app heap: 앱에서 메모리를 할당하는 기본 힙.
  • JNI heap: JNI(Java Native Interface) 참조가 할당되고 해제되는 위치를 보여주는 힙.

오른쪽 메뉴에서 할당을 정렬할 방법을 선택합니다.

  • Arrange by class: 클래스 이름을 기반으로 모든 할당을 분류합니다. 이는 기본값입니다.
  • Arrange by package: 패키지 이름을 기반으로 모든 할당을 분류합니다.
  • Arrange by callstack: 모든 할당을 호출 스택으로 분류합니다.

프로파일링하는 동안 앱 성능 개선

프로파일링하는 동안 앱 성능을 개선하기 위해 메모리 프로파일러는 기본적으로 메모리 할당 샘플을 주기적으로 추출합니다. API 수준 26 이상을 실행하는 기기에서 테스트하는 경우 Allocation Tracking 드롭다운을 사용하여 이 동작을 변경할 수 있습니다. 다음과 같은 옵션을 사용할 수 있습니다.

  • Full: 메모리의 모든 객체 할당을 캡처합니다. Android 스튜디오 3.2 이전 버전의 경우 기본 동작입니다. 앱에서 많은 객체를 할당하는 경우 프로파일링하는 동안 앱이 눈에 띄게 느려질 수 있습니다.
  • Sampled: 일정한 간격으로 메모리에서 객체 할당 샘플을 추출합니다. 기본 옵션으로, 프로파일링하는 동안 앱 성능에 미치는 영향이 적습니다. 단기간에 많은 객체를 할당하는 앱은 여전히 눈에 띄게 느려질 수 있습니다.
  • Off: 앱의 메모리 할당 추적을 중지합니다.

글로벌 JNI 참조 보기

JNI(Java Native Interface)는 자바 코드 및 네이티브 코드가 서로를 호출할 수 있는 프레임워크입니다.

JNI 참조는 네이티브 코드에 의해 수동으로 관리되므로 네이티브 코드에서 사용하는 자바 객체가 너무 오랫동안 연결된 상태로 유지될 수 있습니다. JNI 참조가 먼저 명시적으로 삭제되지 않고 취소되면 자바 힙의 일부 객체에 도달하지 못할 수도 있습니다. 또한 글로벌 JNI 참조 한도를 소진할 수도 있습니다.

이러한 문제를 해결하려면 메모리 프로파일러의 JNI heap 뷰를 사용하여 모든 글로벌 JNI 참조를 둘러보고 자바 유형 및 네이티브 호출 스택을 기준으로 필터링하세요. 이 정보를 사용하여 글로벌 JNI 참조가 생성되고 삭제되는 시기와 위치를 알 수 있습니다.

앱이 실행되는 동안 검사하려는 타임라인 부분을 선택하고, 클래스 목록 위의 드롭다운 메뉴에서 JNI heap을 선택합니다. 그런 다음 평소와 같이 힙에 있는 객체를 검사하고 Allocation Call Stack 탭에서 객체를 더블클릭하여 그림 4와 같이 코드에서 JNI 참조가 할당되고 해제된 위치를 확인할 수 있습니다.

그림 4. 글로벌 JNI 참조 보기

앱 JNI 코드의 메모리 할당을 검사하려면 Android 8.0 이상을 실행하는 기기에 앱을 배포해야 합니다.

JNI에 관한 자세한 내용은 JNI 도움말을 참고하세요.

네이티브 메모리 프로파일러

Android 스튜디오 메모리 프로파일러에는 Android 10 이상을 실행하는 실제 기기 및 가상 기기에 배포된 앱의 네이티브 메모리 프로파일러가 포함되어 있습니다.

네이티브 메모리 프로파일러는 특정 기간 네이티브 코드에서 객체의 할당/할당 해제를 추적하고 다음 정보를 제공합니다.

  • Allocations: 선택한 기간 동안 malloc() 또는 new 연산자를 통해 할당된 객체의 수입니다.
  • Deallocations: 선택한 기간 free() 또는 delete 연산자를 통해 할당 해제된 객체의 수입니다.
  • Allocations Size: 선택한 기간 모든 할당의 집계 크기(단위: 바이트)입니다.
  • Deallocations Size: 선택한 기간 확보되는 모든 메모리의 집계 크기(단위: 바이트)입니다.
  • Total Count: Allocations 열의 값에서 Deallocations 열의 값을 뺀 값입니다.
  • Remaining Size: Allocations Size 열의 값에서 Deallocations Size 열의 값을 뺀 값입니다.

네이티브 메모리 프로파일러

Android 10 이상을 실행하는 기기에서 네이티브 할당을 기록하려면 Record native allocations를 선택한 다음 Record를 선택합니다. 기록은 Stop 을 클릭할 때까지 계속됩니다. 클릭한 후에는 메모리 프로파일러 UI가 네이티브 기록을 표시하는 별도의 화면으로 전환됩니다.

Record native allocations 버튼

Android 9 이하에서는 Record native allocations 옵션을 사용할 수 없습니다.

기본적으로 네이티브 메모리 프로파일러는 32바이트 크기의 샘플을 사용합니다. 메모리 32바이트가 할당될 때마다 메모리의 스냅샷이 만들어집니다. 샘플 크기가 작을수록 스냅샷 생성 빈도가 높아지고 메모리 사용량에 관한 보다 정확한 데이터가 생성됩니다. 샘플 크기가 클수록 데이터의 정확도가 떨어지지만 기록하는 동안 시스템의 리소스가 더 적게 소비되고 성능이 향상됩니다.

네이티브 메모리 프로파일러의 샘플 크기를 변경하려면 다음 단계를 따르세요.

  1. Run > Edit Configurations를 선택합니다.
  2. 왼쪽 창에서 앱 모듈을 선택합니다.
  3. Profiling 탭을 클릭하고 Native memory sampling interval(bytes)이라는 라벨이 지정된 필드에 샘플 크기를 입력합니다.
  4. 앱을 빌드하고 다시 실행합니다.

힙 덤프 캡처

힙 덤프는 힙 덤프를 캡처할 때 앱에서 메모리를 사용 중인 객체를 보여줍니다. 특히 장기적인 사용자 세션 후, 더 이상 메모리에 없어야 한다고 생각되는데 아직 메모리에 있는 객체를 보여주므로 메모리 누수를 식별하는 데 도움이 됩니다.

힙 덤프를 캡처한 후 다음 정보를 확인할 수 있습니다.

  • 앱에서 할당한 개체의 유형 및 각 유형의 개체 수
  • 각 개체가 사용 중인 메모리의 양
  • 코드에서 각 개체의 참조가 유지되는 위치
  • 객체가 할당된 호출 스택 (현재는 할당을 기록하는 동안 힙 덤프를 캡처할 때 Android 7.1 이하에서만 힙 덤프와 함께 호출 스택을 사용할 수 있습니다.)

힙 덤프를 캡처하려면 Capture heap dump를 클릭한 다음 Record를 선택합니다. 힙을 덤프하는 동안 자바 메모리의 양이 일시적으로 증가할 수도 있습니다. 자바 메모리의 양이 증가하는 것은 힙 덤프가 앱과 동일한 프로세스에서 발생하고 데이터를 수집하기 위해 메모리가 필요하므로 정상적인 현상입니다.

프로파일러가 힙 덤프를 캡처한 후에는 메모리 프로파일러 UI가 힙 덤프를 표시하는 별도의 화면으로 전환됩니다.

그림 5. 힙 덤프 보기

덤프가 언제 생성되는지 더 정확히 알아내려면 dumpHprofData()를 호출하여 앱 코드의 중요한 지점에서 힙 덤프를 만들면 됩니다.

클래스의 목록에서 다음 정보를 확인할 수 있습니다.

  • Allocations: 힙의 할당 수.
  • Native Size: 이 객체 유형에서 사용하는 총 네이티브 메모리의 양(바이트). 이 열은 Android 7.0 이상의 경우에만 표시됩니다.

    Android에서 Bitmap 등 일부 프레임워크 클래스에 네이티브 메모리를 사용하므로 자바에서 할당된 일부 객체의 경우 여기에 메모리가 표시됩니다.

  • Shallow Size: 이 객체 유형에서 사용하는 총 자바 메모리의 양(바이트).

  • Retained Size: 이 클래스의 모든 인스턴스로 인해 유지되는 총 메모리 크기(바이트).

할당된 객체 목록 위의 두 메뉴를 사용하여 어느 힙 덤프를 검사할지와 데이터를 구성하는 방법을 선택할 수 있습니다.

왼쪽 메뉴에서 검사할 힙을 선택합니다.

  • default heap: 시스템에서 힙을 지정하지 않은 경우.
  • app heap: 앱에서 메모리를 할당하는 기본 힙.
  • image heap: 부팅하는 동안 미리 로드된 클래스가 포함된 시스템 부팅 이미지. 여기서는 할당이 결코 이동하거나 사라지지 않습니다.
  • zygote heap: Android 시스템에서 앱 프로세스가 포크되는 COW(기록 중 복사) 힙.

오른쪽 메뉴에서 할당을 정렬할 방법을 선택합니다.

  • Arrange by class: 클래스 이름을 기반으로 모든 할당을 분류합니다. 이는 기본값입니다.
  • Arrange by package: 패키지 이름을 기반으로 모든 할당을 분류합니다.
  • Arrange by callstack: 모든 할당을 호출 스택으로 분류합니다. 이 옵션은 할당을 기록하는 동안 힙 덤프를 캡처하는 경우에만 작동합니다. 그렇지만 힙에 기록을 시작하기 전에 할당된 객체가 있을 수 있으므로 이러한 할당이 클래스 이름별로 나열되어 먼저 표시됩니다.

목록은 기본적으로 Retained Size 열을 기준으로 정렬됩니다. 다른 열의 값을 기준으로 정렬하려면 열의 제목을 클릭하세요.

클래스 이름을 클릭하면 오른쪽에 Instance View 창이 열립니다(그림 6 참고). 각 인스턴스에는 다음과 같은 항목이 나열됩니다.

  • Depth: 임의의 GC 루트에서 선택된 인스턴스까지 가장 짧은 홉 수.
  • Native Size: 네이티브 메모리에서 이 인스턴스의 크기. 이 열은 Android 7.0 이상의 경우에만 표시됩니다.
  • Shallow Size: 자바 메모리에서 이 인스턴스의 크기
  • Retained Size: 도미네이터 트리당 이 인스턴스가 지배하는 메모리의 크기.

그림 6. 힙 덤프를 캡처하는 데 필요한 기간이 타임라인에 표시됨

힙을 검사하려면 다음 단계를 따르세요.

  1. 목록을 둘러보고 힙 개수가 비정상적으로 크고 누출될 가능성이 있는 객체를 찾습니다. 알려진 클래스를 찾으려면 Class Name 열 헤더를 클릭하여 알파벳순으로 정렬합니다. 그런 다음, 클래스 이름을 클릭합니다. 오른쪽에 Instance View 창이 표시되고 그림 6과 같이 이 클래스의 각 인스턴스를 보여줍니다.
    • 또는 Filter 를 클릭하거나 Control+F(Mac의 경우 Command+F)를 누르고 검색 필드에 클래스 또는 패키지 이름을 입력하여 신속하게 객체를 찾을 수 있습니다. 드롭다운 메뉴에서 Arrange by callstack을 선택한 경우 메서드 이름으로 검색할 수도 있습니다. 정규 표현식을 사용하려면 Regex 옆의 체크박스를 선택합니다. 대소문자를 구분하는 검색어의 경우 Match case 옆의 체크박스를 선택합니다.
  2. Instance View 창에서 인스턴스를 클릭합니다. 아래에 References 탭이 표시되고 객체에 관한 모든 참조가 표시됩니다.

    또는 인스턴스 이름 옆의 화살표를 클릭하여 모든 필드를 표시한 다음, 필드 이름을 클릭하여 필드의 모든 참조를 확인합니다. 필드의 인스턴스 세부정보를 보려면 필드를 마우스 오른쪽 버튼으로 클릭하고 Go to Instance를 선택합니다.

  3. References 탭에서 메모리 누수가 발생할 가능성이 있는 참조가 식별된 경우 마우스 오른쪽 버튼으로 클릭하고 Go to Instance를 선택합니다. 이렇게 하면 힙 덤프에서 인스턴스가 선택되고 자체 인스턴스 데이터가 표시됩니다.

힙 덤프에서 다음과 같은 이유로 인해 발생한 메모리 누수가 있는지 확인하세요.

  • Activity, Context, View, Drawable 장기 참조 및 ActivityContext 컨테이너 참조를 보유할 수도 있는 기타 객체의 장기 참조
  • Runnable과 같이 Activity 인스턴스를 보유할 수 있는 비정적 내부 클래스
  • 필요 이상으로 오랫동안 객체를 보유하는 캐시

힙 덤프를 HPROF 파일로 저장

힙 덤프를 캡처한 후에는 프로파일러가 실행되는 동안에만 메모리 프로파일러에 데이터가 표시됩니다. 프로파일링 세션을 종료하면 힙 덤프를 잃게 됩니다. 따라서 나중에 검토하기 위해 힙 덤프를 저장하려면 HPROF 파일로 내보내세요. Android 스튜디오 3.1 이하에서는 타임라인 아래 툴바 왼쪽에 Export capture to file 버튼이 있고, Android 스튜디오 3.2 이상에서는 Sessions 창의 각 Heap Dump 항목 오른쪽에 Export Heap Dump 버튼이 있습니다. Export As 대화상자가 표시되면 .hprof 파일 이름 확장자를 사용하여 파일을 저장하세요.

jhat과 같은 다른 HPROF 분석기를 사용하려면 HPROF 파일을 Android 형식에서 자바 SE HPROF 형식으로 변환해야 합니다. android_sdk/platform-tools/ 디렉터리에 제공된 hprof-conv 도구를 사용하여 변환할 수 있습니다. 두 개의 인수 즉, 원본 HPROF 파일과 변환된 HPROF 파일을 작성할 위치를 사용하여 hprof-conv 명령어를 실행하세요. 예:

hprof-conv heap-original.hprof heap-converted.hprof

힙 덤프 파일 가져오기

HPROF(.hprof) 파일을 가져오려면 Sessions 창에서 Start a new profiling session 을 클릭하고 Load from file을 선택한 후 파일 브라우저에서 파일을 선택하세요.

HPROF 파일을 파일 브라우저에서 편집기 창으로 드래그하여 가져올 수도 있습니다.

메모리 프로파일러에서 누수 감지

메모리 프로파일러의 힙 덤프를 분석할 때 Android 스튜디오에서 앱의 ActivityFragment 인스턴스에서 메모리 누수가 의심되는 프로파일링 데이터를 필터링할 수 있습니다.

필터에 표시되는 데이터 유형은 다음과 같습니다.

  • 삭제되었지만 아직 참조되고 있는 Activity 인스턴스
  • 유효한 FragmentManager가 없지만 아직 참조되고 있는 Fragment 인스턴스

다음과 같은 특정 상황에서는 필터에 거짓양성이 표시됩니다.

  • Fragment를 생성하였지만 아직 사용하지 않은 경우
  • Fragment가 캐시되고 있지만 FragmentTransaction의 일부가 아닌 경우

이 기능을 사용하려면 먼저 힙 덤프를 캡처하거나 Android 스튜디오로 힙 덤프 파일을 가져옵니다. 메모리 누수가 발생할 수 있는 프래그먼트와 활동을 표시하려면 메모리 프로파일러의 힙 덤프 창에서 Activity/Fragment Leaks 체크박스를 선택합니다(그림 7 참고).

프로파일러: 메모리 누수 감지

그림 7. 메모리 누수를 일으키는 힙 덤프 필터링

메모리 프로파일링 기법

메모리 프로파일러를 사용하는 동안 앱 코드에 스트레스를 주어 강제로 메모리 누수를 유발해야 합니다. 앱에서 메모리 누수를 유발하는 한 가지 방법은 힙을 검사하기 전에 잠시 동안 앱이 실행되도록 하는 것입니다. 메모리가 힙의 할당 상한까지 서서히 누수될 수 있습니다. 하지만 누수가 적을수록 더 오랫동안 앱을 실행해야 누수를 확인할 수 있습니다.

다음 중 한 가지 방법으로 메모리 누수를 트리거할 수도 있습니다.

  • 기기가 다양한 액티비티 상태에 있는 동안 기기를 세로 모드에서 가로 모드로 회전했다가 다시 되돌리기를 여러 번 반복합니다. 기기를 회전하면 앱에서 Activity, Context 또는 View 객체를 누출하는 경우가 종종 발생할 수 있습니다. 시스템에서 Activity를 다시 만들고, 앱에서 이러한 객체 중 하나의 참조를 다른 곳에 유지하는 경우 시스템에서 가비지 컬렉션을 할 수 없기 때문입니다.
  • 다양한 액티비티 상태에 있는 동안 개발자의 앱과 다른 앱 간을 전환합니다(홈 화면으로 이동한 다음 개발자의 앱으로 돌아옵니다).

팁: monkeyrunner 테스트 프레임워크를 사용하여 위 단계를 실행할 수도 있습니다.