Technical Note/MOBILE PERFORMANCE

1 Intro


일전에 어떤 기업에서 기술면접을 본 적이 있다. 그 때 면접관이 물어봤던 물음 중에 하나가 “안드로이드 플랫폼에서 메모리 누수가 일어나는 경우와 그것을 어떻게 관리할지”에 대한 것이었다. 당시에는 솔직히 잘 몰라서 아주 원론적인 답만 했다. 그게 한동안 계속 기억에 남아서 날 찜찜하게 만들었다.


메모리 누수 문제는 어떤 언어나 플랫폼을 쓰던 꼭 집고 넘어가야 할 문제이다. 나의 경우엔 C와 같이 프로그래머가 직접 메모리를 관리하는 경우에만 익숙해져 있다보니 자동으로 메모리를 관리한다는 Java 같은 언어에서는 어떤 문제가 발생하는지 정확히 알지 못했다.


여담이지만 특정기능을 시스템 차원에서 지원한다는 것이 마냥 좋지만은 않은 것 같다. 뭔가가 ‘자동’으로 이루어진다는 말은 그걸 쓰는 사람이 필요에 따라 그 기능에 직접 개입할 수 없다는 뜻도 되기 때문이다. 설사 직접 개입할 수 있는 길이 있다 하더라도 보통 그런 접근법은 권장되지 않는다. 그 기능의 작동 플로우에 녹아들어 같이 휩쓸려가는 방식이 권장된다. 이런 관계로 자동으로 이루어지는 그 작동 방식을 알아야한다. 모르면 잘못 사용할 가능성이 높다는 것이다. Java의 Garbage Collection도 딱 이런 경우인데, C에서 직접 메모리를 관리하면서 주의해야할 부분과는 또 다른 방식으로 신경을 쓸 수 밖에 없는 것 같다.


암튼 모순적이게도 Java 세상에서도 프로그래머가 직접 메모리 관리에 대해 신경써야 하는 상황이 있으니 그에 대해 한번 정리해보고자 한다.


2 Java에서의 메모리 누수


원론적으로 말하자면 Java의 Garbage Collector(이하 GC)가 제대로 작동할 겨우 메모리 누수가 생길 수 없다. 왜냐하면 GC가 특정 조건을 trigger로 작동하면서 아무도 참조하지 않는 (힙의 특정영역을 점유하고 있을)객체는 회수하기 때문이다1. 반대로 말하자면 회수가 안된 객체는 누군가에 의해 분명 참조되고 있다는 뜻이다. 만약 룰이 깨진다면 그것은 해당 JVM 구현의 버그일 것이다.


그렇다면 Java에서 메모리 누수란 무엇일까? 위의 전제에서 힌트가 있다. 즉 누군가에 의해 참조되고 있는 그 객체가 진정 쓸모있냐 없냐에 달린 것이다. 쓸모없는 객체를 계속 누군가가 참조를 하고 있다면 그게 바로 메모리 누수이다.


Stackoverflow에 있는 재미있는 쓰레드에 보면 Java에서 쓸모없는 객체를 미친듯이 생성시켜 Out Of Memory Exception을 일으키는 여러 경우에 대한 예시가 나온다.


3 Android에서의 메모리 누수


안드로이드에서는 일반적인 JVM과는 달리 프로세스 하나마다 dalvik(JVM)이 하나씩 실행되기 때문에, JVM 하나에 다중 프로세스 때문에 생기는 문제에 대해 신경쓸 필요는 없어서 그나마 좀 간편하다고 하겠다2. 하지만 플랫폼의 구조적인 결함이나 버그로 인한 메모리 누수가 몇가지 알려져 있다. 알아보도록 하자.


3.1 Context 관련


이 문제는 안드로이드 공식 블로그 글에 원인과 해결책이 잘 나와있다. 간단히 말하면 일정한 생명주기를 가지는 Activity 내에서 View 를 생성할 때 Context 즉 해당 Activity 자체를 인자로 넘기는데, 화면 방향전환과 같이 Activity 재생성이 일어나는 경우 관련된 모든 객체들의 참조를 끊지 않으면 Activity 객체가 사용하던 메모리가 회수되지 않아 누수가 발생한다는 것이다.


이 문제는 플랫폼 설계상 어쩔 수 없이 생길 수 있는 것인 듯 하다. 플랫폼 구조를 뜯어고치지 않는 이상 응용프로그래머가 주의하여 프로그래밍 할 수 밖에 없는 상황이라 하겠다.


그럼 해결 방법은 무엇일까? 몇가지가 있다.


Activity Context 대신 Application Context를 사용할 것을 고려하라. Application Context 객체는 Application 단위의 생명주기를 가진다.

Activity Context를 Activity에서 쓸 경우, Activity 생명주기와 싱크를 맞추어라.

만일 Activity의 sub-class를 정의하여 사용한다면, sub-class에서 상위 Activity를 참조하는 경우를 되도록 만들지 마라. 대신 sub-class를 static으로 정의하고 해당 클래스가 Activity와WeakReference 를 갖도록 하라.

3.2 Bitmap 관련3


안드로이드 버전 2.3.3(API Level 10) 이전의 경우 Bitmap 의 픽셀 데이타를 native heap에 저장했다고 한다. 이로 인해서 Bitmap 객체가 GC를 통해 메모리 회수되지 않고 자꾸 쌓여서 OOM Exception을 일으키는 경우이다. 어떻게 보면 말도 안되는 일인데, 이를 해결하기 위해서 Bitmap의 recycle() 4이란 함수를 호출하라고 친철히 설명하고 있다. 최근 버전들은 이 문제가 해결되어(native heap을 쓰지않고 Dalvik VM heap으로 바꾸었다함) 참조변수를 null 로 바꾸기만 해도 충분하다고 한다.


3.3 기타


그 외에도 WebView5 나 MapView6 등에서 누수가 일어나는데 딱히 해결책이 없는 모양이다. 그나마 누수의 크기가 크지 않아서 조심하면서 쓰는 수 밖에 없단다.(뭐 이래~!)


4 마무리


Memory Leak이란 결국 해당 프로그램이 메모리(heap) 부족으로 원하는 시간만큼 실행되지 못할 때 문제가 된다. 잠재적인 위험을 가지고 있지만, 즉 누수가 조금씩 일어나지만 메모리 부족이 생기기 이전에 실행이 끝나고 프로세스가 정상적으로 죽는 경우라면 사실 문제될 이유가 없다. 하지만 그렇게 제한적인 프로그램이 활용도가 높을리는 없을꺼다. 따라서 프로그래머는 메모리 누수를 신경써야 한다. 아주 많이.


5 References


Memory Management in the Java HotSpot Virtual Machine

Java Reference와 GC

Bitmap.recycle()은 가급적 호출해 주는 편이 좋다. (Android 2.x 버전)

안드로이드 메모리 누수 줄이기

https://docs.google.com/present/edit?id=0AUhbwUh9azXLZGZnNnpnYzhfMzFjYnp2NzRjNg

Footnotes:


1 더 정확히 말하면 레퍼런스의 Root Set에 해당객체를 직접적 혹은 간접적으로 참조하는 변수가 없다면 그 객체는 garbage collecting 시에 회수된다.


2 시스템의 아래로 내려가면 결국은 Linux 프로세스 위에 dalvik이 하나씩 떠 있는 상황이라 특정 프로세스에서 메모리릭이 발생하여 그 프로세스가 죽는다고 해도 다른 프로세스에는 영향이 없다.


3 Issue 8488: 'bitmap size exceeds VM budget' if Activity is restarted {includes test demo!}


4 이 함수는 Bitmap 이 객체화 될때 native heap에 할당한 픽셀 데이타를 free 시킨다.


5 http://stackoverflow.com/questions/3130654/memory-leak-in-webview


6 Issue 2181: Memory leak in system when using MapView