top of page

안드로이드 앱 메모리 릭(Memory Leak) 찾기

안녕하세요. AOS개발 Unit Denny입니다.


안드로이드 앱을 개발하다보면 다양한 원인으로 메모리 릭(Memory leak)이 발생[1] 할 수 있습니다. 앱에 메모리 릭이 한 곳이라도 존재하면 앱의 메모리 사용량이 점점 증가하게 되고, 휴대폰의 잦은 GC[2] 수행을 발생시켜서 전반적인 퍼포먼스를 떨어뜨립니다. 이는 사용자의 앱 사용 경험에 부정적인 영향을 미치기에 매우 중요한 해결 과제라고 할 수 있습니다.


그림 1. 메모리 누수 == 개발자의 눈물

이에 본 글에서는 안드로이드 스튜디오에서 메모리 릭[3]을 찾는 방법에 대해 알려 드리겠습니다. 전체적인 순서는 아래와 같고, 메모리 릭을 테스트할 기능이나 화면을 선정한 후, 이것을 반복 수행하여 안드로이드 스튜디오에서 확인하는 과정으로 메모리 릭을 찾고 수정할 수 있습니다.

  1. 메모리 릭을 테스트할 기능 / 화면 선정

  2. 테스트할 기능 반복 수행

  3. 안드로이드 스튜디오에서 메모리 확인

  4. 메모리릭 수정



1. 메모리 릭을 테스트할 기능 / 화면 선정

앱 전체 기능을 한번에 테스트하고 메모리릭을 찾는 것은 매우 어렵습니다. Static으로 선언되어 공용으로 사용되는 변수나 이벤트의 경우, 릭을 발견하더라도 어느 기능에서 릭이 발생했는지 명확한 포인트를 선별하기 어렵기 때문입니다. 따라서 한 화면, 한 기능씩 테스트를 하고 릭 발생 여부를 체크해나가는 것이 중요합니다.

따라서 메모리릭 체크를 처음한다면, 앱의 첫화면부터 진행하는 것이 좋습니다. 첫 화면에서 메모리 릭이 없다면, 다음 화면이나 기능을 하나씩 선정하여 메모리 릭 체크를 해나가면 됩니다. 이처럼 메모리 릭은 한번에 해결하기보단 지속적으로 다양한 화면과 기능을 하나씩 체크해야하며, 메모리 릭이 더 이상 발견되지 않을 때는 신규 기능과 변경점을 중심으로 체크하면 좋습니다.




2. 테스트할 기능 반복 수행

테스트할 기능을 선정했다면, 앱을 debug 빌드후, [그림3]처럼 해당 기능을 반복적으로 수행합니다. 이때 중요한 것은 해당 기능의 시작과 종료에 따라 명확히 메모리가 해제되는 지점을 기준으로 해야합니다. 예를 들어 Activity 라면, Activity 가 onCreate 된 후, onDestory 가 호출될 때 까지 반복테스트 해야하며, Activity 에서 선언된 변수와 callback 등이 모두 해제되는 기준으로 테스트합니다.


그림 2. 반복 테스트

앱의 첫 화면을 테스트한다면 {앱 실행 - back key 로 화면 종료}를 반복합니다. Activity 가 종료되더라도 App의 Process는 남아있기 때문에, 다시 앱을 실행하더라도 동일한 process에서 Activity가 수행됩니다. 만약 Activity에 메모리 릭이 있다면 이전 실행에서 남아있던 메모리가 해제되지 않아 메모리 릭을 쉽게 발견할 수 있습니다.

주의 할 점은 테스트 중에 앱을 강제 종료하면 안됩니다. 강제 종료 시 앱에서 할당받은 메모리가 모두 해제되고, 재실행시 새로운 process로 시작되기에 메모리 릭을 발견할 수 없습니다.




3. 안드로이드 스튜디오에서 메모리 확인

위처럼 반복 수행을 했다면, [그림3]과 같이 안드로이드 스튜디오 하단에서 Profiler 를 선택하고 SESSIONS에서 + 버튼으로 분석할 앱을 선택합니다. Profiler에는 앱의 CPU, 메모리, 네트워크 사용량에 대한 정보를 알 수 있습니다.[4]


그림 3. Profiler 실행

Profiler에서 메모리 영역을 선택하면 메모리에 대한 자세한 내용을 확인할 수 있습니다. [그림4]와 같이 메모리 누수를 찾기 위해 휴지통 모양의 아이콘을 여러 번 클릭하여 강제 GC를 수행합니다. 이 동작후 GC로도 해제되지 않은 메모리를 검출할 수 있습니다. 그런 다음 Capture heap dump를 선택하고 Record를 선택합니다.


그림 4. Profiler Memory 분석

[그림5]처럼 Leaks 항목이 0 으로 뜨면 테스트한 화면/기능에서 메모리 릭이 없다는 의미입니다.


[그림 5] 메모리릭이 없는 heap dump 결과

[그림6]은 실제 FLO에서 발견한 메모리 릭으로 LockScreenPlayerActivity에서 발생한 결과입니다. Leaks 18 이라고 떠있는데, Leaks 부분을 클릭하면 메모리가 해제되지 않은 Class를 보여줍니다. 18개가 떴다고 해서 18군데에 메모리릭이 발생한 것이 아니라, 해당 기능을 6번 반복 테스트 했기에 reference 숫자 만큼 보여지는 것이며, 만약 context가 해제 되지 않고 살아있다면 해당 reference까지 모두 잡히기에 SupportRequestManagerFragment, ReportFragment까지 검출되게 됩니다. 중요한 것은 앱의 클래스 중 첫번째 항목부터 분석하는 것입니다.


[그림 6] FLO에서 발생한 메모리릭

첫번째 항목을 선택하고, instance 에서도 첫번째 항목을 선택하면 [그림7]처럼 오른쪽에 Intance Details 항목이 나타나는데, References 탭을 선택합니다. References 에서도 첫번째 항목을 선택하고 child 로 타고 타고 내려가다보면 의심스러운 변수명을 발견할 수 있습니다. 사실, 여기서부터는 개발자의 경험과 센스가 필요합니다. LockScreenPlayerActivity내의 어느 변수, 콜백이 Leak이라고 알려주진 않습니다.

그림에서 보면, LockScreenPlayerActivity의 메모리가 해제되지 않은 이유는 AppFloxPlayer 클래스에서 사용 중인 floxStateListenerList 변수때문일 뿐입니다. 이제부터, LockScreenPlayerActivity와 AppFloxPlayer의 관계를 찾으면 됩니다.


[그림 7] Instance Details



4. 메모리릭 수정

위 결과에서 LockScreenPlayerActivity에서 AppFloxPlayer를 참조하는지 찾아봅니다. 아래 [표1]처럼 여러가지의 코드를 찾았습니다. 과연 어느 것이 문제일까요?



네 아주 쉽죠. 3번입니다. 이번 메모리릭은 아주 쉬운 편이었습니다. LockScreenPlayerActivity의 멤버 변수인 stateCallback 변수를 singleton AppFloxPlayer에 listener로 등록하고, Activity가 destroy 될 때 해제를 하지 않는 것이 문제였습니다.

이번 leak은 다행히 Profiler 에서 검출된 첫번째 항목, 첫번째 reference child만으로도 추적이 가능했습니다. 더구나 변수명과 원인이 된 메소드명도 유사해서 찾기가 더 쉬웠습니다. 만약, 첫번째 항목들에서 유추가 불가능하다면 여러 Reference를 찾아봐야 할 것입니다.

이제 onDestroy에 stateCallback을 해제하는 코드를 넣고, 다시 반복 테스트를 하고 Profiler를 돌려보고, 메모리 릭이 검출되지 않는다면 정확하게 릭을 해결한 것입니다. 만약, 릭이 여전하다면 다른 reference를 확인해보고 원인을 찾는 과정을 반복해야 합니다. 또한, 상황에 따라 같은 동작인데도 또 다른 릭이 발견 될 수 있으니, 끈기를 갖고 테스트하고 Profiler로 분석하고 릭을 해결하는 과정을 반복해야 합니다.



마무리

제 경험 상 listener를 등록하고 해제하지 않거나, Observable 에 PropertyChangedCallback 을 등록하고 해제하지 않는 경우들이 메모리 릭을 발생 시키는 가장 많은 실수였습니다.

메모리 릭의 영향도가 가장 컸던 부분은 Activity의 멤버 변수의 릭이였습니다. Activity의 멤버 변수가 다른 Class에 참조되어 메모리 해제가 되지 않으면, 그 Activity 까지도 메모리가 완전히 해제가 되지 않아서, 사용하면 할수록 메모리 사용량의 증가가 비대해지는 영향을 끼쳤습니다.

지금까지 메모리릭을 찾는 방법을 설명했는데요, 앱을 개발하면서 항상 메모리 릭을 점검하고 배포하는 습관을 들이면 좋을 것 같습니다. 확실히, 메모리릭을 해결하면 할수록 앱의 성능 향상이 체감 되니깐요. 긴 글 읽어주셔서 감사합니다.



참고자료


댓글


bottom of page