LeakCanary란?

LeakCanary는 안드로이드를 위한 메모리 누수 감지 라이브러리다.

안드로이드 프레임워크 내부 이해를 통해 메모리 누수의 원인을 줄이는 기능을 제공하여, 개발자가 OOM(Out Of Memory)에러로 인한 크래시를 줄일 수 있도록 도와준다.

메모리 누수란?

애플리케이션이 더 이상 필요하지 않은 객체에 대한 참조를 유지함으로써 결과적으로 해당 객체에 할당 된 메모리를 회수 할 수 없어 나타나는 오류.

실생활에 비유해보자. 일반적으로 락커룸에서는 사용자가 락커 이용을 마친 뒤 키를 꽂아두고 나오거나 반납을 해야한다. 하지만 고의 또는 실수로 키를 가지고 집으로 돌아오게 되면 다른 사용자는 해당 락커를 이용할 수 없다. 키를 반납하는 사례가 없이 이런 일들이 누적되면 락커룸에 이용할 수 있는 락커는 없어지게 된다. 새롭게 락커 이용을 원하는 사용자가 락커룸을 들어왔는데 사용할 수 있는 락커가 없을 수 있다. 그런 상태를 컴퓨터 메모리 관점에서는 OOM이라고 부른다.

안드로이드에서는 OOM이 발생하는 경우를 예를 들어본다면, Activity 인스턴스는 onDestroy() 호출 이후  더 이상 필요없게 된다. 하지만 어딘가에서 해당 Activity의 인스턴스를 강하게 참조하고 있다면 Garbage Collection의 대상이 되지 않으므로 메모리 누수가 발생했다고 볼 수 있다.

설정하기

app 모듈 레벨의 build.gradle에 다음과 같이 설정한다.

dependencies {
    // debug 빌드에서만 동작하도록 하기 위해 debugImplementation로 선언한다.
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}

메모리 누수 사례

메모리 누수를 잘 잡는지 확인하기 위해 몇가지 테스트를 진행한다.

1. Activity 메모리 누수 사례

구글에서 하지말라는 것을 해보자.

ViewModel은 특정 Activity 인스턴스보다 수명이 길 수 있기 때문에 Acitivty Context 참조해서는 안 된다.

class MainViewModel : ViewModel() {

    var activity:Activity? = null

}
class MainActivity : AppCompatActivity() {

    private val viewModel:MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if(savedInstanceState==null){
            viewModel.activity = this // 하지 말라는 것 해보기 ㅎㅎ
        }
    }

}

이제 Activity 재생성을 위해 화면회전 옵션을 활성화 하고 가로모드, 세로모드로 1회 왕복 한다.

다음과 같이 메모리 누수가 발생하는 것을 확인할 수 있다.

 

2. View Binding 메모리 누수 사례

이전 포스팅을 참고하자

3. RecyclerView 메모리 누수 사례

이건 좀 마이너한 경우이긴 한데, RecyclerView.Adapter가 RecyclerView보다 더 수명이 긴 경우에 Adapter에서 View를 참조하면서 메모리 누수가 발생한다는 메시지를 LeakCanary를 통해 확인할 수 있다.

그럴때는 다음과 같이 RecyclerView가 참조하고 있는 Adapter를 null로 초기화해주면 간단히 해결할 수 있다.

override fun onDestroyView() {
    recyclerView.adapter = null
    super.onDestroyView()
}

만약 트랜지션 애니메이션을 사용해야해서 Adapter를 null로 초기화 할 수 없는 상황이라면 다음과 같은 방법을 적용하고, View가 detached 상태가 되었을 때 Adapter를 정리할 수 있다.

override fun onDestroyView() {
    recyclerView.addOnAttachStateChangeListener(object:View.OnAttachStateChangeListener{
        override fun onViewAttachedToWindow(v: View?) {
            // nothing to do
        }

        override fun onViewDetachedFromWindow(v: View?) {
            recyclerView.adapter = null
        }
    })
}

LeakCanary는 어떻게 동작하는 걸까?

LeakCanary가 설치된 뒤 다음 4단계를 거쳐 메모리를 자동으로 감지하고 리포트 한다.

  1. retained 객체를 감지한다
  2. 힙 메모리 내용을 덤프한다(메모리 내용 복사)
  3. 힙 메모리 분석
  4. 메모리 누수내용을 항목화

1. Retained 객체 감지하기

LeakCanary는 안드로이드 생명주기를 알고 자동으로 Activity, Fragment, View, ViewModel 파괴시에 ObjectWatcher로 넘겨져 GC 대상인지 확인하게 된다. 파괴 후 5초이내에 GC되지 않으면 retained되었다고 판단하여 로그캣에 다음과 같은 메시지를 출력한다

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객체들의 갯수를 새고 알림(Notification)을 통해 보여준다.

notification

2. 힙 메모리 덤프하기

Retained 객체수가 임계 값에 도달하면 LeakCanary는 Java 힙 메모리 영역을 Android 파일 시스템에  .hprof 파일로  덤프한다. 힙을 덤프하면 짧은 시간 동안 앱이 정지되고, 그 동안 토스트와 함께 상태가 좋지 못한(?) 카나리아를 확인할 수 있다. 하지만 실제 카나리아는 귀엽다

toast

3. 힙 메모리 분석하기

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

done

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

done

알림을 탭하면 Activity가 시작되고 더 자세한 정보를 확인할 수 있다. 

4. 메모리 누수 항목화 하기

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

Library Leak

마치며

쉽고 강력한 메모리 누수 감지 라이브러리인 LeakCanary에 대해서 알아보았다. 자바 or 코틀린은 자동으로 가비지 콜렉션이 이루어지지만 그렇다고해서 개발자가 메모리에 대한 아무런 경각심 없이 프로그래밍을 할 수 있다는 의미는 아니다. 개발은 아무나 할 수 있지만 조금 더 세세한 내용까지 신경쓰면서 개발하는 것은 정말 어려운 일인 것 같다. LeakCanary에 대한 자세한 내용 및 메모리 누수를 해결 하는 방법은 공식 문서에서 확인가능하다.

카테고리: 미분류

2개의 댓글

jason · 2021년 9월 28일 6:40 오후

I love u so much :>

    Charlezz · 2021년 9월 28일 10:38 오후

    oh no, what the heck..

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.