[Android] LeakCanary로 메모리릭 잡기
Android

[Android] LeakCanary로 메모리릭 잡기

728x90

 

LeakCanary란?

LeakCanary란 Square사에서 만든 오픈소스 라이브러리로써, 메모리릭을 감지하여 OOM(Out of Memory) 에러를 줄일 수 있도록 도와준다.

 

 

메모리릭(Memory leak)이란?

 메모리릭이란 애플리케이션에서 더 이상 필요로 하지 않는 객체의 참조를 가지고 있는 것이다. 이렇게 되면 해당 객체에 할당된 메모리를 회수 할 수 없게 되고OOM을 발생시킨다.

 안드로이드에서는 주로 객체의 라이프사이클과 관련해서 많이 발생한다. 예를 들어, Activity는 onDestroy() 함수 호출 이후엔 더 이상 필요 없어지게 되며 GC의 대상이 되어 메모리 할당을 해제한다. 하지만 어디선가 Activity를 강하게 참조하고 있다면, GC의 대상에서 제외되서 메모리릭이 발생하게 된다.

 

 

LeakCanary 동작 방식

1. retained 객체 감지

 LeakCanary는 안드로이드 생명주기를 알고 Activity, Fragment, View, ViewModel이 파괴됐을 때 자동으로 감지하여 ObjectWatcher로 전달하게 된다. 파괴 후 5초 이내에 GC가 되지 않으면 retained 된 것으로 판단하여 이를 Logcat에 기록한다.

D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
  (Activity received Activity#onDestroy() callback) 

... 5 seconds later ...

D LeakCanary: Scheduling check for retained objects because found new object
  retained

 이후 LeakCanary는 힙을 덤프하기 전에 retained 객체들의 수를 세고 알림으로 표시한다.

 

2. 힙 메모리 덤프

 retained 객체수가 임계값을 넘어가면 LeakCanary는 Java 힙 메모리 영역을 안드로이드 파일 시스템에 .hprof 파일로 덤프한다. 힙을 덤프하면 짧은 시간동안 앱이 정지되며, LeakCanary는 아래와 같은 토스트를 띄워준다.

 

3. 힙 메모리 분석

 LeakCanary는 Shark라는 도구를 이용하여 .hprof 파일을 분석하고, 덤프된 힙에서 retained 객체를 찾아낸다.

 분석이 완료되면 로그캣 및 알림을 통해 분석 결과를 확인할 수 있다.

====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS

Displaying only 1 leak trace out of 2 with the same signature
Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
┬───
│ GC Root: Local variable in native code
│
...

 

4. 누출 분류

 LeakCanary는 앱에서 찾은 메모리릭을 애플리케이션 누수와 라이브러리 누수로 분류한다. 라이브러리 누수는 타사 코드의 버그로 인해 발생한 메모리릭이다. 이 라이브러리 누수는 응용 프로그램에 영향을 주지만 안타깝게도 제어할 수 없으므로 LeakCanary에서 분리하게 된다.

 

 

LeakCanary 설정하기

 LeakCanary를 설정하는 방법은 정말 간단하다. app 모듈 레벨의 build.gradle 파일에 아래와 같이 설정한다. LeakCanary는 디버그 빌드에서만 동작하도록 하기 위해 debugImplementation으로 선언한다.

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}

 

 

 

 

LeakCanary로 메모리릭 잡기 예제

 메모리릭이 발생하는 사례는 여러가지가 있지만, 가장 간단하게 할 수 있는 방법은 클래스의 정적 필드에서 Activity를 참조하고 있는 것이다.

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        activity = this
    }

    companion object {
        var activity: MainActivity2? = null
    }
}

 간단한 액티비티를 하나 만든 뒤, onCreate() 함수에서 해당 객체를 정적 필드에 담아준다. 그 뒤, 이 액티비티를 종료시키면 메모리릭이 발생하게 된다. 메모리릭이 발생하면 아래와 같은 로그캣이 기록되며, LeakCanary 앱을 통해서도 확인할 수 있다.

 로그캣을 볼때는 아래쪽부터 보는 것이 편하다. Leaking: YES 라고 표시된 부분이 메모리릭이 발생했다는 것이다. 로그캣을 보면 MainActivity2 instance Leaking: YES라고 기록되었으므로 MainActivity2로 찾아가 어떤 메모리릭이 발생했는지 확인해보면 된다.

 

 

 위에서 설명한 예제 이외에도 메모리릭이 발생하는 경우가 많다. 자바 혹은 코틀린이 자동으로 GC가 이루어진다고 해도 개발자의 실수로 인하여 메모리릭이 발생하고 앱의 성능을 저하시킬 수 있다. LeakCanary를 사용하면 쉽고 간단하게 메모리릭을 방지할 수 있다. 이외의 자세한 내용은 공식 문서에서 확인이 가능하다.

 

 

참고

square.github.io/leakcanary/fundamentals-how-leakcanary-works/

728x90