티스토리 뷰

Android/Android Multi Threading

6. 메모리 관리

안드로이드용용 2016. 7. 29. 16:51

지난 시간에 안드로이드 스레드, 프로세스간 통신 기법과 원리에 대해서 장시간 설명했다.

오늘은 쉬어가는 의미에서 안드로이드 메모리 관리는 어떻게 되는가에 대해서 간단히 살펴보도록 하자. 


메모리 관리가 왜 중요한가?

메모리 누수는 응용프로그램에 있어서 crash를 초래할 뿐만 아니라 기기 전체에 영향을 미쳐서 성능을 저하시키므로 메모리 관리가 필수적이다.

우리는 오늘 프로그램의 정확한 설계를 통해 스레드와 관련된 메모리 누수의 위험을 관리하기 위한 전략을 배워보도록 하자.


6.1 가비지 컬렉션

달빅 VM은 메모리가 너무 커질 때, 힙이라는 공유 메모리로부터 가비지 컬렉터(GC)로 할당된 메모리를 주기적으로 회수하는 메모리 관리 시스템이다. 


힙 외부에서 접근할 수 있는 모든 객체는 GC루트로 간주되고, 스레드에서 직접 또는 간접적으로 참조된 객체는 스레드의 실행 중에 도달이 가능하다.


6.2 스레드 관련 메모리 누수

응용프로그램 스레드는 실행 중에 GC가 메모리를 회수하는 것을 방해할 수 있으므로 메모리 누수를 일으킬 잠재적 위험을 가지고 있다.


스레드 관련 메모리 누수에는 두 가지 중요한 특징이 있다.


1. 잠재적 위험

메모리 누수의 위험은 스레드가 살아있고 객체 참조를 유지하는 시간과 함께 증가한다. 수명이 긴 스레드가 더 이상 필요하지 않은 객체에 참조를 유지하는 것이 그것이다.


2. 누수 크기

비트맵, 뷰 계층 구조 같이 누수의 크기가 크면 적은 수의 누수도 응용프로그램의 힙을 고갈시킬 수 있다.


응용 프로그래머는 가능한 메모리에 생길 수 있는 위험을 줄이고 잠재적 누수를 적게 유지하도록 구현되어야 한다.


6.2.1 스레드 실행

스레드에서 작업을 실행하는 응용 프로그램은 Thread와 Runnable 인스턴스를 사용한다.

작업자 스레드의 생명주기

1. 스레드 객체를 생성한다. (Thread 객체가 인스턴스화된다.)

2. 스레드 실행을 시작한다. (스레드가 시작되고 Runnable인스턴스가 실행된다.)

3. 스레드가 종료된다. (Runnable 인스턴스가 실행을 종료한다.)


2단계에서 스레드가 실행될 때, thread 객체 자체는 GC루트가 되고, 참조하는 모든 객체는 GC루트로 도달할 수 있다. 객체를 다른 메서드에서 참조할 수 있도록 메서드가 자신의 호출자에 객체를 반환하지 않으면 메서드 안에서 생성된 객체는 메서드가 결과를 반환하는 시점에 가비지 컬렉션의 대상이 된다.


내부 클레스

내부 클래스는 외부 클래스처럼 자신을 둘러싸는 객체의 맴버이고 모든 다른 멤버를 접근할 수 있다. 따라서 내부 클래스는 암시적으로 외부 클래스에 대한 참조를 가진다. 

지역 클래스 및 익명 내부 클래스도 마찬가지 원리이다.


정적 내부 클래스

객체의 클래스 인스턴스 멤버이다. 외부 객체 클래스에 대한 참조를 유지한다. 이는 자신을 참조하는 다른 개게가 사라지면 가비지 컬렉션이 될 수 있다.


생명 주기 불일치

안드로이드에서 누수에 대한 근본적인 이유는 구성요소(Activity, Service, Broadcast Reciver, Content Provider),객체, 스레드 사이의 생면주기가 불일치 하기 때문이다.

특히 Activity 객체의 누수가 가장 심각하고 일반적이다. 엑티비티는 수많은 힙 메모리를 할당하고 뷰 구조에 대한 참조를 담고 있다.


Activity의 생명주기는 onCreate에서 시작되어 onDestroy에서 종료되기 때문에 onDestroy 콜백이 발생할 때 메모리 누수가 있는지 확인해 보아야 한다.

onDestroy가 불리는 경우

  • 사용자가 뒤로 가기 버튼을 누를 때
  • Activity가 명시적으로 finish()를 호출할 떄
  • 시스템이 응용프로세스가 종료될 수 있도록 결정할 때
  • 설정 변경(configuration change)이 일어날 때, Activity 회전
Activity 종료 후에도 구성요소의 객체들이 힙에 공존하는지 봐봐야 한다. 엑티비티가 종료 되더라도 Activity에서 생성한 백그라운드 스레드가 시작되었으면, 메모리 누수의 문제의 위험을 초래할 수 있다.


6.2.2 스레드 통신

스레드 실행은 메모리 누수의 잠재적인 원인이 될 수 있고 이는 스레드 간 전달 메커니즘에서 비롯된다. 된다. 


데이터 메시지 전송

데이터 메시지는 다양한 방법으로 전달될 수 있고, 구현에 따라 메모리 누수의 위험과 크기를 결정한다.

public class Outer {
    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
           // 메시지 처리
       }
    };
    
    public void doSend() {
        Message message = mHandler.obtainMessage();
        message.obj = new SampleObject();
       mHandler.sendMessageDlayed(message, 60 * 1000);
       }
    }
}

쓰레드가 outer object의 모든 객체를 참조하므로 필요이상으로 참조를 가지고 있고, 오랫동안 참조를 유지한다.


태스크 메시지 발송

Runnable을 발송하는 것은 메시지 전송과 동일한 우려를 낳는다.

public class Outer {
    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
           // 메시지 처리
       }
    };
    
    public void doSend() {
       mHandler.post(new Runnable() {
            public void run() {
            // 긴 실행 테스크
           }
       }); 
    }
}

마찬가지로 메모리 누수에 대한 위험은 테스크 길이와 함께 증가된다.


6.3 메모리 누수 방지

앞에서 설명했듯이 메모리 누수는 필요 이상으로 오래 메모리에 남아 있는 객체에 의해 발생한다.


6.3.1 정적 내부 클래스 사용

정적 내부 클래스는 인스턴스 객체가 아닌 오직 전역 클래스 객체에 의해 참조되기 때문에 누수를 완화 시킬 수 있다.


6.3.2 약한 참조 사용

정적 내부 클래스의 단점은 외부 클래스의 인스턴스 필드에 접근할 수 없다. 이는 외부 인스턴스에 접근 하고자 할 때 제약사항이 될 수 있다. 이 때, java.lang.ref.WeakReference를 사용할 수 있다.

public class Outer {
       private int mField;
       private static class SampleThread extends Thread {
            private final WeakReference mOuter;

           SampleThread(Outer outer) {
               mOuter = new WeakReference(outer);
          }
          
         @Override
         public void run() {
             //외부 클래스 인스턴스 필드를 실행 및 수정하라.
            // 외부 인스턴스가 가비지 컬렉션 되었는지 null을 확인하라.
            if (mOuter.get() != null) {
                 mOuter.get().mField = 1;
           }
      }
    }
}

약한 참조는 일반 참조(강한참조)와는 달리 가비지 컬렉터의 참조 카운팅 대상이 아니다. 따라서 GC가 힙에서 메모리를 해제시킬 수 있다.


6.3.3 작업자 스레드 실행 중지

스레드가 필요 없을 때, 명시적으로 null로 해제하는 방법


6.3.4 작업자 스레드 유지

일반적으로 긴 시간은 엑티비티의 설정 변경에서 발생하는데 이런 설정 변경은 스레드가 실행되는 한 오래된 객체가 메모리 안에 유지되도록 한다. 오래된 엑티비티에서 새로운 엑티비티로 스레드를 유지하고 스레드 참조를 제거함으로써 Activity객체가 GC 대상이 될수 있도록 한다.


6.3.5 메시지 큐 정리

메시지가 큐에서 더이상 필요하지 않을 때, 해제될 수 있도록 메시지 큐에서 제거한다.


removeCallbacks(Runnable r)

...


Message는 what과 token 식별자로 제거할 수 있다.


6.4 마치며

안드로이드 메모리 관리는 프로그램을 하는데 있어서 매우 중요하다. 특히 긴 작업의 스레드를 설계할 때 반드시 메모리 누수의 여부가 없는지 확인 해 보아야 한다. 


이로서 안드로이드 멀티 스레딩 1부 정리가 끝났다. 차주에는 멀티 스레딩 기법의 구체적인 방법에 대해서 2부를 설명하도록 하겠다.




'Android > Android Multi Threading' 카테고리의 다른 글

8. 핸들러 스레드  (0) 2016.08.03
7. 기본 스레드의 생명주기  (0) 2016.08.01
5. 프로세스 간 통신  (0) 2016.07.28
4. 스레드 통신  (0) 2016.07.26
3. 안드로이드 스레드  (0) 2016.07.25
댓글
댓글쓰기 폼
공지사항
최근에 달린 댓글
Total
65,392
Today
3
Yesterday
1
TAG
more
«   2022/10   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
글 보관함