Heap Size Limit
안드로이드 앱에는 Heap Size 의 Limit 가 존재합니다. Heap Size 의 Limit 은 Device dependent합니다. 단말마다 다른 heap size limit 을 가지고 있다는 것이죠. 최초의 안드로이드 단말 G1 의 경우는 어플리케이션 하나가 가질 수 있는 heap memory 는 16MB 였습니다. 다시 말해, 어플리케이션 하나가 16MB 이상의 Heap 을 사용하는 경우 OutOfMemoryError 가 발생하며, Application Crash 현상이 발생한다는 것입니다. 모바일 단말의 하드웨어 스펙이 발달하면서, Heap Size Limit 은 증가하기 시작했습니다. Droid 의 경우 24M, Nexus One 의 경우 32M, Xoom 의 경우 48M 까지 증가했으며, 현재 High-end 급의 단말들은 더 높은 메모리 사이즈를 지원하겠죠.
이 단말의 Heap Size Limit 은 API 코드로도 확인할 수 있습니다.
int ActivityManager.getMemoryClass()
// returns memory heap limit size in MB
더 큰 Heap Memory 를 사용할 순 없는가?
Android SDK 3.0 HoneyComb 이상부터는 manifest 설정을 통해 기본 단말에서 제공하는 Heap memory size 보다 더 큰 용량을 사용할 수 있습니다. application tag 에 largerHeap = "true" 값을 넣어주면 됩니다.
<application
....
largerHeap="true">
이 largerHeap 의 값도 device dependent 하다고 하니, ActivityManager.getMemoryClass() 를 통해서 사용 가능한 메모리 용량을 확인해 보시면 되겠습니다.
largerHeap="true" 옵션은 주의해서 사용하셔야 합니다.
먼저 Larger Heap Size 는 그만큼 GC 시간을 더 소비한다는 의미이며, 다른 App들을 죽일 수 있습니다.
따라서 신중히 사용되어야 합니다.
Garbage Collection.
2012/01/12 - [프로그래밍 놀이터/자바] - [Java] Garbage Collection ( GC ) 가 뭐 하는 녀석인지 한 번 해부해보자.
Pre-GingerBread
Android SDK 2.3 GingerBread 이전의 Garbage Collection 은 다음의 특징을 가집니다.
- Stop-the-world
- Full heap Collection
- Pause times often > 100ms.
From GingerBread
GingerBread을 포함하여 이후의 버전들의 Garbage Collection 은 다음의 특징을 가집니다.
- Concurrent ( mostly )
- Partial collections.
- Pause times usually < 5ms.
GC 의 개선은 매우 혁신적이라고 할 수 있습니다.
먼저 Stop-the-world 가 아닌 Concurrent 방식으로 바뀌었기 때문에, 반응성 측면이 상당히 좋아졌습니다.
게다가 partial collection 형태로 GC 시간이 단축되어 pause time 이 보통 5ms 라고 하네요.
이제 개발하면서, GC가 발생할까 걱정하는 시기는 지났다고 보여집니다.
( 물론 메모리 관리를 잘 해서 GC 는 가급적 불리지 않는 것이 가장 좋습니다. )
Bitmap 에 대한 이야기 ( Native Memory )
안드로이드 앱에서 Memory 를 가장 많이 소모하는 녀석이 바로 bitmap 이라고 할 수 있습니다.
Phone 용 App 에서는 보통 50% 가 BItmap 이 소모하였으며,
Tablet 과 같이 큰 화면에서는 더 많은 bitmap 을 display 하기 때문에 70% 이상을 소모한다고 하네요.
Pre-HoneyComb
Android SDK 3.0 HoneyComb 이전에는 Bitmap Object 를 실제 Bitmap 과 상관없이 Heap Memory 에 동일 사이즈로 할당을 합니다. 즉 Container 의 역할만 하는 것이지요. 그리고 실제 BItmap의 Pixel 값들은 Native 에 할당이 되어 있는 형태입니다.
- freed via recycle() or finalizer
- hard to debug
- full, stop-the-world GCs
From Honey Comb
HoneyComb 을 포함한 이후 버전에서는 Bitmap Object 를 다른 방식으로 관리하고 있습니다. HoneyComb 이전 버전과 마찬가지로, Bitmap Object 는 동일하게 생성하지만, Native 에 할당하던 Pixel memory 를 Davik Heap 으로 위치를 옮긴 것이지요.
- freed synchronously by GC
- easier to debug
- concurrent & partial GCs
Log 를 통한 메모리 분석
D/dalvikvm( 9050 ): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K paused 2ms+2ms.
GC에 대한 로그정보를 통해 "개략적인" 메모리 사용 실태를 확인할 수 있습니다.
Reason For GC
위 로그의 "GC_CONCURRENT" 파트가 GC를 초래한 이유가 되겠습니다.
GC_CONCURRENT 이외에도 다른 이유로 GC 가 초래되는데 다음과 같은 이유들이 있습죠.
- GC_CONCURRENT
- GC_FOR_MALLOC : GC가 수행 or 끝나기 전에 malloc 이 불려서 memory 가 full 이 된 경우
- GC_EXTERNAL_ALLOC : external native memory alloc 이 발생하여 memory 가 full 이 된 경우
( HoneyComb 부터는 방식이 바뀌어, Bitmap 이나 NIO 의 ByteBuffer 류에는 이 GC 가 발생하지 않습니다. )
- GC_HPROF_DUM_HEAP
- GC_EXPLICIT
Amount Freed
"freed 2049K" 정보가 GC 를 통해 해제된 메모리 량입니다.
약 2MB 정도가 해제되었네요.
Heap Statistics
"65% free 3571K/9991K" 이 부분이 Heap의 통계를 보여줍니다.
위에서 Free 된 2049K 가 현재 사용하던 메모리 총 량의 65% 라는 것을 나타내며,
GC 후 남아있는 사용하는 메모리가 3571K 이고, total memory size 가 9991K 라는 것을 나타냅니다.
External Memory Statistics
"external 4703K/5261k" 부분이 external memory statistics 이며,
4703K 가 현재 alloc 된 memory, 5261K 가 soft limit 를 나타냅니다.
Pause Time
"paused 2ms + 2ms" 부분이 pause time을 나타냅니다.
이렇게 2개의 숫자가 더해지는 경우는 Concurrent GC 일 때 발생을 하며, Stop-the-world GC의 경우 1개의 숫자만 표시됩니다. 첫 2ms 는 GC의 준비시간을 의미하며, 두번째 GC는 실제 GC의 collection 작업의 수행 시간을 말합니다. 합하여 4ms 로 매우 짧은데, Stop-the-world GC 의 경우 훨씬 긴 pause time 을 갖는다고 합니다. ( 100ms 를 훌쩍 넘는다고 하네요. )
MAT ( Memory Analyzer ) 와 Heap Dump
2012/02/24 - [프로그래밍 놀이터/안드로이드] - [Android/안드로이드] MAT ( Mermory Analyzer Tool ) Android, Eclipse 사용 방법.
Android 에서는 Heap Memory 정보를 얻을 수 있는 Heap Dump 를 제공합니다.
DDMS 상에서 Heap Dump 아이콘을 클릭하여 dump file 을 얻을 수도 있고,
코드상에서 Debug.dumpHprofData() 를 통해 추출해낼 수도 있습니다.
이렇게 얻어진 heap dump file 은 heap memory information 을 가진 binary로, MAT 에서 읽기 위해서는 conversion이 필요합니다. command 창에서 다음과 같이 입력하면 되며, hprof 의 경우 android sdk 에 포함되어 있습니다.
> hprof -conv orig.hprof converted.hprof
Prerequisite
MAT 을 사용하기에 앞서 다음과 같은 용어들과 개념들을 이해하셔야 합니다.
Leak : 사용하지 않는 Object 를 참조하고 있어 GC 를 방해하는 것. ( C, C++ 의 leak 과는 조금 차이가 있습니다. )
- Context 를 자체 생명주기보다 오래 참조하는 것은 leak 을 초래하는 지름길.
- Activity, Conterxt, View, Drawable 등을 long-live reference 유지하는 것은 매우 위험한 일.
- Non-static inner class와 Runnable 은 enclosing object 를 참조하기 때문에 생명주기와 synchronize 시키는 것이 중요.
- 무분별한 Caches 는 OutOfMemory 와 Leak 을 초래하기 쉬움.
- Shallow heap : object 하나가 가지는 byte 용량을 나타냄.
- Retained heap : GC가 어떤 object 를 해지했을 때, 해지되는 총 메모리량으로, 한 object 가 참조하는 녀석들을 tree 형태로 따라가며 더한 값으로, 매우 중요한 정보를 갖는다.
[특정 process id 정보만 log 보기]
> adb logcat | grab [Process ID]
MAT 사용하기.
Histogram View
사용하는 모든 메모리의 합을 object 별로 sum 하여 나타내준 graph 입니다.
사실 graph 라고는 하지만 list 형태로 나오죠.
Memory leak 을 추적할 때는 추적해보죠.
( 참고로, byte[] 가 최상단으로 올라오기 쉬운데, 이는 bitmap 이 이제 heap memory 에 assign 되기 때문입니다. )
object를 우클릭 -> [List Object] -> [with incoming reference]
어떤 녀석이 해당 object 를 참조하고 있는지를 보여줍니다. with outgoing reference 는 해당 object 가 참조하는 녀석들이 나오겠죠?
object 우클릭 -> [Path To GC Roots] -> [Exclude Weak References]
Weak Reference 야 어차피 GC 와 크게 상관이 없으니, exclude 하고, 유용한 정보만 확인합니다. 이것을 통해 GC Root 를 찾아냄으로서, 의도하지 않은 Root 를 가지고 있지는 않은지 확인 할 수 있습니다.
Filter 기능
Activity나 Context 의 경우는 보통 하나를 유지합니다. 하지만, 경우에 따라 Leak 이 발생한 경우에는 Activity의 갯수가 2개 혹은 그 이상이 되는 경우를 발견할 수 있습니다. Activity 이름이나, 갯수를 명확히 알고 그것이 유지되어야 하는 경우에는 Histogram View의 상단에 Filter 기능을 사용하여 Filtering 하면, 해당 Object 의 갯수를 쉽게 파악할 수 있습니다.
Dominator tree
Dominator tree 는 어떤 object 가 어떤 object 를 참조하고 있는지를 보여주는 tree 입니다. 사실 이것이 실제 참조 그대로를 보여주는 것이 아니고, 살짝 fake 라고 할 수 있는 logic 으로 tree 를 구성하는데, 사실 이 fake 된 tree 가 memory leak 을 잡기에는 훨씬 수월합니다. 말로 설명은 어렵고, 아래 그림을 통해서 이해하시길 바랍니다.
안드로이드의 경우 resource 를 많이 사용하기 때문에, Dominator tree 에서는 resource 가 보통 가장 위로 올라오게 됩니다. "항상" 은 아니지만 대부분 이 Resource object 자체는 크게 신경쓰지 않아도 됩니다.
Object 우클릭 -> [Path To GC Roots] -> [exclude weak references]
Histogram 과 마찬가지로, GC Roots 를 탐색하여, 참조의 root 를 찾을 수 있습니다.
참고 사이트
구글 Video Clip 에서는 친절하게도, 참조 사이트까지도 알려주고 있습니다.
저도 아직 방문하지 않앗지만, 이 글을 포스팅 한 이후에 한번 방문해보려고 합니다.
제목부터가 상당히 attractive 합니다.
Markus Kohler's Java Performance Blog
http://kohlerm.blogspot.com/
'Programing > Android' 카테고리의 다른 글
Process and Thread (0) | 2016.05.04 |
---|---|
Processes and Application Life Cycle (0) | 2016.05.04 |
Avoiding Memory Leaks (0) | 2016.05.03 |
Activity, Fragment Lifecycles (0) | 2016.05.03 |
Handler, Looper (0) | 2016.04.19 |