본문 바로가기
안드로이드

[Android 문서번역] Avoiding Memory Leaks (메모리 누수를 피하는 방법)

by 호군 2011. 10. 23.
반응형

아래와 같은 방법으로 최대 힙 메모리와 사용되고 있는 힙 메모리를 구할 수 있다고 합니다.
그런데 어플에서 사용되고 있는 힙 메모리의 크기는 정확히 구하지를 못한다는군요.

System.gc();
long lTotMemory = Runtime.getRuntime().maxMemory() / (1024*1024);
long lUseMemory = lTotMemory - ( Runtime.getRuntime().freeMemory() / (1024*1024));





원문 : http://developer.android.com/resources/articles/avoiding-memory-leaks.html


T-Mobile G1폰의 경우, 안드로이드 어플리케이션들에 할당되는 Heap 메모리 사이즈는 최대 16MB로 제한됩니다.
그것은 단말에게는 충분한 메모리일 수 있는 반면 어떤 개발자들에게는 매우 부족한 양일 수도 있습니다.


이 메모리 전체를 사용할 계획이 없다 하더라도,
다른 어플리케이션들이 종료되지 않도록 하기 위해
최소한의 메모리 사용을 유지할 필요는 있습니다.

(안드로이드가 더 많은 어플리케이션들을 메모리상에 유지할수록

사용자는 어플리케이션간 전환 동작이 더욱 빠르게 실행됨을 느낄 수 있게 됩니다.)

 

안드로이드 어플리케이션 개발 중 발생하는 대부분의 메모리 누수는

오랜 시간 동안 Context 참조를 유지하기 때문에 발생합니다.

 

안드로이드 상에서 Context는 여러 가지 동작들을 수행하기 위해 사용되는데,

특히 리소스들을 읽고 접근하기 위한 용도로 주로 사용됩니다.

 

표준의 안드로이드 어플리케이션에서 일반적으로 사용하게 되는

두 가지 종류의 Context "Activity" "Application" 이며,
개발자가 Context를 필요로 하는 클래스나 메소드에 항상 첫 번째로 전달하는 인자입니다.

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
 
  TextView label = new TextView(this);
  label.setText("Leaks are bad");
 
  setContentView(label);
}


위의 코드는 View가 전체 Activty 및 이 Activity가 소속된 모든 것들에 대한 참조를

갖고 있음을 의미합니다. 그러므로 만약 Context 누수가 발생한다면, (여기서 누수란 참조가 유지되고 있기 때문에 가비지 컬렉션(GC)이 일어나지 못함을 의미) 이에 따른 많은 메모리 누수가 발생하게 됩니다.

 

순간 방심하면 전체 액티비티에 대한 누수는 쉽게 발생할 수 있습니다.

 

화면 전환(screen orientation)이 일어날 때 시스템은 기본적으로 현재상태를 저장(onSaveInstanceState)하고 액티비티를 종료(onDestroy)시킨 뒤, 저장된 상태를 다시 읽어(onRestoreInstanceState) 액티비티를 다시 시작(onCreate) 하게 됩니다.

그 과정에서 안드로이드는 어플리케이션의 UI를 리소스들로부터 다시 읽어오게 됩니다.

화면 전환이 일어날 때마다 거대한 비트맵이 포함된 어플리케이션의 리소스들을 매번 다시 읽어오는 끔찍한 상황을 상상해 보십시오.

 

비트맵을 매번 다시 읽어오지 않게 하기 위한 가장 쉬운 방법은 정적(static) 필드를 사용하는 것입니다.

private static Drawable sBackground;
 
@Override
protected void onCreate(Bundle state) {
 
super.onCreate(state);
 
 
TextView label = new TextView(this);
  label
.setText("Leaks are bad");
 
 
if (sBackground == null) {
    sBackground
= getDrawable(R.drawable.large_bitmap);
 
}
  label
.setBackgroundDrawable(sBackground);
 
  setContentView
(label);
}


이 코드는 매우 빠르기는 하지만 잘못된 코드입니다.

첫 화면 전환이 일어날 때 첫 Activity의 누수가 발생합니다.

 

DrawableView에 연결될 때 ViewDrawable callback으로 설정 됩니다.

 

위의 코드에서 Drawable TextView에 대한 참조를 갖게 되며

TextView 자체도 액티비티(Context)에 대한 참조를 갖습니다.

또한 작업된 코드 내용에 따라 그 밖의 추가적인 것들을 참조할 수도 있습니다.

 

Context 누수 처리 예제를 Home screen 소스코드를

통해 확인할 수 있습니다. (unbindDrawables() 메소드를 찾아 보십시오 ->

DrawablecallbackActivity가 종료되는 시점에 null로 설정하는 코드)

 

매우 흥미롭게도 Context 누수의 연쇄 사슬을 만들 수 있는 경우들이 있으며,

이것은 Out of Memory 에러 발생을 가속시키게 됩니다.

 

Context 관련 누수를 피하는 두 가지 방법이 있습니다.

 

첫 번째 확실한 방법은 Context 자신의 범위 밖으로 벗어나는 것을 피하는 것이며,

(위의 코드는 정적 참조의 경우를 보여주나 정적 내부 클래스와 외부클래스에 대한

암묵적인 참조는 똑같이 위험합니다.)

 

두 번째 방법은 Application Context를 사용하는 것입니다.

Context는 어플리케이션이 살아있는 동안만 지속되며 Activity 생명주기에

의존하지 않습니다.

Context를 필요로 하는 긴 수명의 객체가 필요하다면 Application 객체를 기억하기 바랍니다. (Context.getApplicationContext() 또는 Activity.getApplication()을 통해 획득 가능)

 

요약하자면.. Context 관련 메모리 누수를 피하기 위해서는 아래 사항들을 기억하세요.

 

* Context-activity에 대한 참조를 오랫동안 유지하지 마십시오.

* Activity에 대한 참조는 Activity 자신과 동일한 생명주기를 가져야 합니다.

* Activity Context 대신 Application Context를 사용하세요.

* 생명주기를 직접 제어할 수 없다면 정적이지 않은(non-static) 내부클래스를 Activity안에 사용하는 것을 피하고,
  
정적 내부클래스를 사용할 경우 Activity에 대한 약한 참조(Weak Reference)를 사용하세요.
   (ViewRoot
W 내부 클래스의 구현을 참고)

 

* 가비지 컬렉터가 메모리 누수에 대한 확실한 보험은 아님을 기억하세요.


반응형