[안드로이드앱성능최적화] MAT을 이용한 메모리누수 점검
MAT(Memory Analysis Tool) 이란?
[SLIDE_01]
[SLIDE_02]
eclipse plug-in으로 MAT 설치하고 실행하기
[SLIDE_03]
[SLIDE_04]
[SLIDE_05]
분석결과 보기
[SLIDE_06]
[SLIDE_07]
[SLIDE_08]
[SLIDE_09]
[SLIDE_10]
[SLIDE_11]
[SLIDE_12]
[SLIDE_13]
[SLIDE_14]
[SLIDE_15]
[SLIDE_16]
[SLIDE_17]
[SLIDE_18]
[SLIDE_19]
[SLIDE_20]
[SLIDE_21]
HCGellary 앱을 이용한 메모리 누수 모니터링 해보기
안드로이SDK설치폴더/sample 폴더에 가면 "Honey Comb Gellary" 앱을 사용하여 메모리 누수 모니터링 작업을 수행해 본다.
sample프로젝트는 메모리릭이 발생되지 않도록 작성된 프로그램이다.
우리는 여기서 메모리가 문제가 발생하도록 소스코드를 약간 수정한 뒤 테스트 해보도록 하겠다.
리소스로 부터 읽어온 Bitmap 객체를 ImageView에 붙여서 보여주는 작업을 수행하는데 우리는 이 Bitmap을 HashMap에 캐싱해서 한번 본 이미지에 대한 처리 속도를 높이도록 프로그램을 다음과 같이 수정한다.
[SLIDE_22]
[SLIDE_23]
[SLIDE_24]
[SLIDE_25]
LogCat에는 OOM(Out-of-Memory)에 어느 파일에 몇번째 라인에서 발생했는지에 에러 메시지를 확인할 수 있다. 우리는 좀더 자세한 OOM원인과 상태를 확인하기 위해 해당 프로세스에 대한 메모리 덤프를 확보하고 MAT로 분석 해보려고 한다.
[SLIDE_26]
[SLIDE_27]
Histogram View에 의하면 byte[] 클래스의 Shallow Heap과 Retained Heap이 앱이 사용할 수 있는 Heap 90% 이상을 차지하는 것을 확일 할 수 있다.
[SLIDE_28]
해당 클래스 객체를 참조하는 클래스 객체정보를 확인해 보니 SBitmapCache의 누적된 값이 무척이나 큰것을 확일할 수 있다. 이것은 메모리 누수의 원인으로 판단가능한 데이터이다.
[SLIDE_29]
위 예제의 해결책은?
위 예제의 문제점은 Bitmap 이미지를 캐시하기위해 사이즈제한이 없는 HashMap을 사용하여 캐싱하다 OOM이 발생한 경우이다. 이런 경우에는 LruCache 을 이용하여 이미지 캐시를 하도록 프로그램을 수정할 수 있다.
LruCache은 메모리캐시를 구현하는 API 로 지정된 사이즈만큼 캐시한다. 캐시 사이즈보다 더 많은 데이터를 저장해야 하는 경우 LRU 알고리즘에 따라 가장 최근에 사용하지 않은 데이터를 밀어내고 새로운 데이터를 저장하는 방식을 사용한다.
private static HashMap<String,Bitmap> sBitmapCache = new HashMap<String,Bitmap>();
==> private static LruCache mMemoryCache;
LruCache의 사이즈를 앱의 heap메모리 한계의 1/8 정도로 설정하려고 한다면 프래그먼트가 액티비티에 붙을 때 호출되느 onAttach() 메소드를 재정의하여 캐쉬의 사이즈를 설정하도록 한다.
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// 앱이 사용할 수 있는 Heap Size
final int memClass =((ActivityManager)(activity.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE))).getMemoryClass();
// 힙사이즈의 1/8 을 캐시영역으로 할당하기 위해 캐시크기 계산
cacheSize=1024*1024*memClass/8;
mMemoryCache=new LruCache(cacheSize);
}
// 캐시에 저장되어 있지 않으면 Bitmap을 메모리에 캐싱한다. public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemoryCache(key) == null) { mMemoryCache.put(key,bitmap); } } // 해당 키값으로 이미지가 캐시되어 있으면 Bitmap을 찾아서 반환한다. public Bitmap getBitmapFromMemoryCache(String key) { return (Bitmap)mMemoryCache.get(key); }
void updateContentAndRecycleBitmap(int category, int position) { mCategory = category; mCurPosition = position; if (mCurrentActionMode != null) { mCurrentActionMode.finish(); } // 캐시에 키로 사용할 ID를 생성한다. String bitmapId=""+category+"."+position; // 캐시로 부터 Bitmap 이미지를 찾아온다. mBitmap= getBitmapFromMemoryCache(bitmapId); // 캐시된 이미지가 없으면 Resource로 부터 이미지를 읽어서 Bitmap으로 반환하고, 캐시에 저장한다. if ( mBitmap == null ) { mBitmap = Directory.getCategory(category).getEntry(position) .getBitmap(getResources()); addBitmapToMemoryCache(bitmapId, mBitmap); } // 읽어온 Bitmap 이미지를 화면에 보여준다. ((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap); }