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단계를 거쳐 메모리를 자동으로 감지하고 리포트 한다.
- retained 객체를 감지한다
- 힙 메모리 내용을 덤프한다(메모리 내용 복사)
- 힙 메모리 분석
- 메모리 누수내용을 항목화
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)을 통해 보여준다.
2. 힙 메모리 덤프하기
Retained 객체수가 임계 값에 도달하면 LeakCanary는 Java 힙 메모리 영역을 Android 파일 시스템에 .hprof 파일로 덤프한다. 힙을 덤프하면 짧은 시간 동안 앱이 정지되고, 그 동안 토스트와 함께 상태가 좋지 못한(?) 카나리아를 확인할 수 있다. 하지만 실제 카나리아는 귀엽다
3. 힙 메모리 분석하기
Shark라는 도구를 이용하여 .hprof파일을 파싱하고, 덤프 된 힙에서 Retained 객체들을 찾아낸다.
분석이 끝나면 로그캣 및 알림을 통해 분석결과를 확인할 수 있다.
알림을 탭하면 Activity가 시작되고 더 자세한 정보를 확인할 수 있다.
4. 메모리 누수 항목화 하기
LeakCanary는 앱에서 찾은 누수를 애플리케이션 누수와 라이브러리 누수 두 가지 항목으로 구분한다. 라이브러리 누수는 제어 할 수없는 타사 코드의 버그로 인해 발생하는 누수다. 이 누수는 응용 프로그램에 영향을 주지만 안타깝게도 문제를 해결하지 못할 수 있으므로 LeakCanary에서 분리하게 된다.
마치며
쉽고 강력한 메모리 누수 감지 라이브러리인 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..