본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[도서 정리] 안드로이드 앱 성능 최적화 #5 메모리 성능

by 돼지왕 왕돼지 2018. 6. 26.
반응형

안드로이드 앱 성능 최적화 #5 메모리 성능


이 글은 “안드로이드 앱 성능 최적화” 의 일부 내용만 정리한 것입니다.

자세한 내용은 책을 구매하여 보세요~

\ahead of time, Allocation Tracker, android runtime, android:largeheap, AOT, Art, art 런타임, clean memory, compacting gc, dalvik, dirty memory, dumpsys meminfo, dumpsys meminfo <packageName>, dumpsys meminfo <pid>, dumpsys procstats <packageName>, fragment, garbage collection, garbage collector, GC, gc 성능개선, getMemoryClass, hprof-conv, java runtime, JIT, Just in time, large object space, largeheap, leakcanary, Los, mark and sweep, mat, onTrimMemory, Perceptible, persistent, process stats, procstats, proportional set size, pss, semi space gc, TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_COMPLETE, TRIM_MEMORY_MODERATE, TRIM_MEMORY_RUNNING_CRITICAL, TRIM_MEMORY_RUNNING_LOW, TRIM_MEMORY_RUNNING_MODERATE, TRIM_MEMORY_UI_HIDDEN, Visible, zygote, zygote process, 가비지 컬렉션, 공유 메모리, 그래픽, 더티 메모리, 메모리 모니터링, 메모리 성능, 메모리 정리, 메모리 파편화, 메인 힙, 백그라운드 gc, 비트맵 자체 힙, 비트맵 힙, 안드로이드 메모리, 안드로이드 메모리 부족 경고, 안드로이드 앱 성능 최적화 #5 메모리 성능, 안드로이드 컴파일, 압축 gc, 앱이 사용하는 메모리 용량 알아내기, 이미지 los, 자바 런타임, 전용 메모리, 클린 메모리, 프로세스 통계, 힙 덤프, 힙 심층 분석

5.1. 안드로이드 메모리는 어떻게 동작하는가


-

안드로이드 기기에서 구동되는 자바 런타임(Dalvik 이나 ART)은 메모리 관리 환경이다.

일반적으로 런타임에서 모든 메모리 할당과 해제(GC)를 처리한다.




5.1.1. 공유 메모리와 전용 메모리


-

모든 앱에서 공통으로 사용하는 프레임워크 클래스, 리소스, 네이티브 라이브러리 등이 있다.

안드로이드는 메모리를 절약하려고 이런 것을 공유 메모리에 올려두고 앱 사이에서 함께 사용한다.

공유 메모리는 메모리 사용량을 분석할 때 프로세스에 균등하게 1/n 으로 나누어 적용한다.



-

전용 메모리는 특정 앱 내부에서 사용되고 다른 앱에서 사용할 수 없는 영역이다.



-

Zygote 는 프레임워크 클래스, 리소스, 네이티브 라이브러리를 미리 로딩해서 가지고 있는 프로세스이다.

앱이 시작되면 앱의 개별 코드를 로딩하기 전에 먼저 Zygote 프로세스를 복제한다.

복제된 프로세스들은 프레임워크 클래스, 리소스, 네이티브 라이브러리를 공유해서 사용하게 된다.

이런 내부 동작 때문에 앱은 아무런 준비 없이 로딩되는 것보다 훨씬 더 빠르게 시작할 수 있다.




5.1.2. 더티 메모리와 클린 메모리


-

더티 메모리는 데이터가 램 영역에만 저장되어 있다.

램에서 제거되면 데이터를 다시 얻기 위해 앱이 재실행되어야 한다.



-

클린 메모리는 랩에 올라와 있는 데이터가 이미 디스크에도 동일하게 저장되어 있는 상태이다.

클린 메모리가 제거된 후에 다시 기기로부터 그대로 읽기만 하면 된다.



-

ART 런타임의 가장 큰 특징은 설치할 때 앱이 컴파일(AOT, Ahead Of Time) 된다는 것이다.

반면 Dalvik 런타임은 앱이 구동되는 사이마다 자주 실해앟는 부분만을 컴파일하는 JIT(Just In Time)방식을 사용한다.

ART 가 구동되는 기기에서는 앱 코드가 모두 설치 시점에 컴파일되어 디스크에 저장된다.

이는 클린 영역이라 여겨지고, 디스크로부터 복구가 쉽기 때문에 메모리 부족 시 메모리에서 쉽게 제거할 수 있다.

ART 를 쓴 이후로 메모리 내에 로딩된 앱의 코드를 클린 메모리로 판단해 ART 에서의 메모리 관리는 더욱 향상되었다고 볼 수 있다.




5.1.3. 메모리 정리(가비지 컬렉션)


-

킷캣 이하 버전에서는 GC 는 mark and sweep 방식으로 작동되었고, 메모리 파편화가 발생하여, free 한 메모리 총량보다 실제 가용할 수 있는 메모리 총량이 훨씬 적은 현상이 발생하곤 했다.



-

롤리팝부터 Dalvik 대신 ART 런타임이 사용되기 시작하면서 가비지 컬렉션이 또 개선되었다.

ART 의 모토인 “GC가 방해가 아니라 도움이 되어야 한다” 를 지키기 위해 시스템이 멈추는 횟수는 두 번에서 한번으로 줄었고 ( 기존에는 GC 시작 시점 & 종료 시점 이렇게 두번 ), GC 의 횟수도 줄어 들게 되었다.

실제 GC 에 걸리는 시간도 10ms 에서 3ms 정도로 더 짧아졌다.

비트맵과 같은 큰 객체도 메모리 관리를 단순하게 하려고 자체적인 힙을 할당받게 되었다.



-

ART 에 새로운 GC 알고리즘이 많이 적용되었지만 그 중 흥미로운 것은.. 앱이 포그라운드에 표시되지 않을 때 수행되는 “Semi-Space GC” 이다.

앱이 포그라운드에 표시되지 않을 때 메모리 내에 오브젝트를 재배열하는 방법이다.

메모리에서 객체가 이동하게 되면 앱은 오류를 피하려고 잠시 멈춰야 한다.

이 과정에서 쟁크가 발생할 수 있기 때문에 앱이 포그라운드에 표시되지 않을 때만 Semi-Space GC 가 작동한다.

사실 이 녀석은 완전한 압축 GC 는 아니지만 미사용 메모리에 연속된 큰 영역을 만들어주는 데 매우 유용하다.


\ahead of time, Allocation Tracker, android runtime, android:largeheap, AOT, Art, art 런타임, clean memory, compacting gc, dalvik, dirty memory, dumpsys meminfo, dumpsys meminfo <packageName>, dumpsys meminfo <pid>, dumpsys procstats <packageName>, fragment, garbage collection, garbage collector, GC, gc 성능개선, getMemoryClass, hprof-conv, java runtime, JIT, Just in time, large object space, largeheap, leakcanary, Los, mark and sweep, mat, onTrimMemory, Perceptible, persistent, process stats, procstats, proportional set size, pss, semi space gc, TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_COMPLETE, TRIM_MEMORY_MODERATE, TRIM_MEMORY_RUNNING_CRITICAL, TRIM_MEMORY_RUNNING_LOW, TRIM_MEMORY_RUNNING_MODERATE, TRIM_MEMORY_UI_HIDDEN, Visible, zygote, zygote process, 가비지 컬렉션, 공유 메모리, 그래픽, 더티 메모리, 메모리 모니터링, 메모리 성능, 메모리 정리, 메모리 파편화, 메인 힙, 백그라운드 gc, 비트맵 자체 힙, 비트맵 힙, 안드로이드 메모리, 안드로이드 메모리 부족 경고, 안드로이드 앱 성능 최적화 #5 메모리 성능, 안드로이드 컴파일, 압축 gc, 앱이 사용하는 메모리 용량 알아내기, 이미지 los, 자바 런타임, 전용 메모리, 클린 메모리, 프로세스 통계, 힙 덤프, 힙 심층 분석


-

최신 버전에서 지원하는 압축 GC 방식 ( Compacting GC )

    2015 년 AOSP 에 압축 GC 방식에 대한 project 가 들어 있다.

    이 방식은 메모리의 조각 모음 용도로 메모리의 위치를 이동시킬 때 발생하는 여러 문제를 완화시킬 수 있을 것으로 보인다.

    압축 GC 는 Semi-Space GC 보다 한 단계 더 나아가서, 새로운 메모리 영역으로 복사하는 것 대신 현재 있던 현재 사용하고 있는 영역쪽으로 메모리를 다시 써서 fragment 를 줄인다.

    압축 GC 방식이 지원되면 C/C++ 과 NDK 코드가 잘 작동하는지 꼭 확인해봐야 한다.



-

I/art (10821) : Explicit concurrent mark sweep GC freed 5124(199KB) AllocSpace objects, 1(16KB) LOS objects, 31% free, 34MB/50MB, paused 1.238ms total 23.656ms


1.238ms 동안 UI 를 멈추고 실행되었다.

23.656ms 동안 GC 가 수행되었다.

앱이 사용하는 힙 크기는 50MB, 앱 내에서 34MB 를 사용중이며 여유 공간은 31%.

AllocSpace 에서 5,124 개체 정리했으며, 199KB 의 여유공간이 생겼다.

Large Outpost Space(LOS)에서는 Large Object 를 1개  정리해서 16KB 의 공간이 생겼다.




5.1.4. 앱이 사용하는 메모리 용량 알아내기


-

한 앱이 사용할 수 있는 매모리 양은 ActivityManager.getMemoryClass() 메서드를 통해 알 수 있다.



-

메모리가 많이 사용되는 앱을 만든다면 매니페스트에 android:largeHeap=“true” 를 추가할 수도 있다.

그러나 이 경우 가비지 컬렉션에 걸리는 시간이 함께 늘어날 수 있으므로 주의해야 한다.



-

adb shell dumpsys meminfo 를 통해 메모리 정보를 얻어 올 수 있다.


PSS (Proportional Set Size)는 앱에서 사용하는 총 메모리양이다.

총 메모리양은 전용 메모리와 앱 사이에 공유되는 메모리 크기의 1/n 을 더한 수치이다.


persistent 분류는 시스템 UI, NFC, 전화 기능 같이 기기가 구동되는 동안 항상 실행되어야 하는 프로세스의 목록이다.


Visible, Perceptible 은 화면에 표시되거나 오버레이로 표시되는 앱들


A Services, B Services, Cached 는 BG 로 내려가 있지만 메모리를 할당받은 앱.



-

adb shell dumpsys meminfo <packageName> or <pid> 를 통해 특정 앱의 메모리 정보만 볼 수도 있다.



-

ART 에서는 그래픽들이 메인 힙에 있는 large object space(LOS) 에 저장된다. 이 공간은 일반적으로 많은 메모리를 사용하는 비트맵 객체를 따로 관리해서 가비지 컬렉션을 좀 더 원활하게 하고, 메모리 파편화를 줄여 좀 더 작은 힙으로도 앱이 원활히 구동될 수 있게 해준다.




5.1.5. Procstats


-

meminfo 는 명령을 실행하는 그 순간의 메모리 상태에 대한 정보를 제공한다.

그러나 메모리 누수는 보통 앱을 지속적으로 사용할 때 발생하므로, 킷캣에서는 일정 기간 동안 앱 메모리 사용량을 모니터링 해주는 procstats 가 도입되었다.


개발자 옵션에서 프로세스 통계(Process Stats)를 선택하면 기기 메모리 사용량을 화면에서 바로 확인해 볼 수 있다.

녹색, 노란색, 빨간색 순으로 메모리 문제의 심각도를 표현한다.

바를 클릭하면 메모리 상태에 대한 세부 정보가 나타난다.


메뉴를 통해 포그라운드, 백그라운드, 캐시에 대한 정보도 볼 수 있다.



-

adb shell dumpsys procstats <packageName> 은 지난 24시간, 3시간, 현재 통계를 보여준다.


SOn/Off 는 Screen On/Off 이다.

Norm, Mod, Low, Crit 은 각각 Normal, Moderate, Low, Critical 을 의미한다.


메모리 표시는 MB 단위이며, 총 메모리양의 Low-Average-High / 전용 메모리의 Low-Average-High 로 표시된다.




5.1.6. 안드로이드 메모리 부족 경고


-

앱이 실행중이거나 캐시 상태에서 onTrimMemory 메서드가 불린다. 상황에 따라 메모리를 해제해야 한다는 것을 알려준다.


앱이 실행되고 있는 중에는…


TRIM_MEMORY_RUNNING_MODERATE

    첫번째 경고

TRIM_MEMORY_RUNNING_LOW

    노란 불이 켜졌다. 성능 향상을 위해 리소스를 정리하라는 두번째 경고

TRIM_MEMORY_RUNNING_CRITICAL

    빨간 불이 켜졌다. 메모리 정리 없이 앱을 계속 구동하면 시스템은 백그라운드 프로세스를 없애 필요한 메모리 공간을 확보한다. 



앱의 visibility 가 변했을 때


TRIM_MEMORY_UI_HIDDEN

    앱이 화면에 보이지 않게 된 것을 의미한다. 이 때는 큰 UI 리소스를 정리하는 게 좋다.

    이 메시지를 받고 나면 앱이 캐시된 앱 목록에 표시된다.

    메모리 부족이 계속되는 경우 캐시된 프로세스가 제거될 수 있다.

    백그라운드 상태가 되면 앱을 새로 시작하는 것보다는 빠르게 구동될 정도로만 메모리를 말끔히 정리해 두는 것이 좋다.



앱이 background LRU list 에 있을 때


TRIM_MEMORY_BACKGROUND

    앱이 제거 대상 목록의 끝 부분에 추가된 것을 의미

TRIM_MEMORY_MODERATE

    앱이 제거 대상 목록의 중간 쯤에 위치하고 있음을 의미

TRIM_MEMORY_COMPLETE

    곧 제거될 것이라는 신호






5.2. 자바의 메모리 관리와 누수





5.3. 메모리 누수를 추적하기 위한 도구


5.3.1. 힙 덤프




5.3.2. Allocation Tracker



5.3.3. 메모리 누수 추가하기




5.3.4. 힙 심층 분석 : MAT 와 LeakCanary


-

저장된 힙 덤프는 안드로이드 고유 형식으로 되어 있다.

다른 도구를 사용해 파일을 열려면 파일을 변환해야 하며, 변환도구는 hprof-conv 라는 도구를 사용하면 된다.

hprof-conv <existing_filename> <converted_filename>


단, 안드로이드 스튜디오를 사용하면 이 변환 과정이 자동으로 수행되어 스튜디오 내에서 바로 확인 할 수 있다.




5.3.5. 이클립스 메모리 분석기 도구 (MAT)


-

Eclipse 에서는 IDE 플러그인 형태로 설치되어 있는데, 안드로이드 스튜디오와 함께 사용하려면 독립된 앱으로 된 버전을 Eclipse.org 에서 다운받아 설치하면 된다.



-

메모리 누수가 byte[] 에 저장된 이미지와 관련되어 있다는 것을 알았다면 어떤 이미지인지 확인 할 수 있다.

byte[] 의 중첩된 mbuffer 라는 android.graphics.Bitmap 클래스 객체를 찾아서 우클릭, [Copy] -> [Save value to file] 을 선택한 후 파일로 저장하면 이미지로 저장할 수 있다.




5.2.6. LeakCanary


-

스퀘어 앱에서 발생한 메모리 부족 오류를 줄이기 위해 개발된 녀석으로, MAT 을 통해 하는 일을 자동화해주는 오픈소스이다.

( Canary 는 석탄광산의 카나리아가 광산이 무너지기 전에 알려주는 것처럼 메모리 누수를 알려준다는 의미 )


스퀘어는 LeakCanary 사용 후 OOM 크래시를 94% 줄였다고 한다.



-

build.gradle 에 compile dependency 를 추가해준다.


Application 의 onCreate 에서 install 을 시켜준다.


trace 하고 싶은 녀석을 RefWatcher 에 등록해준다.


Leak 이 발생하면 Logcat log 로 이를 출력하며, 노티도 띄워 노티를 두르면 자세한 정보를 보여주는 Activity 로 이동한다.



-

LeakCanary 를 사용하면 성능저하가 있기 때문에 debug 버전에서만 사용해야 한다.





5.4. 결론


-

아주 최근까지도 메모리 누수를 찾아낼 수 있는 방법은 메모리 부족 크래시와 메모리 참조 문제를 MAT 로 세밀히 분석하는 방법뿐이었다.

하지만 LeakCanary 의 등장으로 일상적으로 MAT 을 사용하는 경우가 많이 줄었다.


안드로이드 앱이 메모리를 어떻게 처리하는지 잘 알고 구현하면 앱 메모리가 제한적인 기기에서 앱을 더욱 효율적으로 구동할 수 있게 되고 메모리 부족으로 인한 크래시 횟수로 줄게 될 것이다.

객체 생성을 제한하고 수명을 적절하게 관리하면 GC 로 인해 메인 스레드가 차단되는 경우를 좀 더 줄일 수 있다.




반응형

댓글