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

Efficient Android Threading #4 메모리 관리

by 돼지왕 왕돼지 2018. 3. 20.
반응형

Efficient Android Threading #4 메모리 관리


이 글은 Efficient Android Threading 의 일부 내용만 발췌한 내용입니다.

자세한 내용은 책을 구입해서 보세용.

activity, activity 객체 참조, back 버튼, CLASS, class 참조, concurrent gc, configuration change, dalvik gc, Efficient Android Threading #4 메모리 관리, Finish, garbage collection, garbage collector, GC, handleMessage, handler, inner class, mark, mark & swap, object, OnDestroy, Outer, removecallbacks, removemessages, Runnable, static inner class, static 내부 클래스, stop the world gc, swap, thread, ui rendering, vm, weak reference, 가비지 컬렉션, 가비지 컬렉터, 객체, 구성 요소, 내부 클래스, 뒤로 가기 버튼, 마크, 메모리 누수, 메모리 누수 방지, 메시지 큐, 메시지 큐 정리, 명시적 finish, 명시적 참조, 사용자 탐색, 생명주기, 설정 변경, 스레드 관련 메모리 누수, 스레드 사이, 스레드 생명주기, 스레드 통신, 시스템, 시스템 자원, 실행 환경, 암시적 참조, 앱 스레드 중지, 약한 참조, 외부 클래스 참조, 익명 클래스, 자동 시작 스레드, 자체 스레드, 작업자 스레드 실행 중지, 작업자 스레드 유지, 테스크, 프로세스, 허니컴

6.1. 가비지 컬렉션


-

각 프로세스(결과적으로 각 앱)는 자신만의 VM 과 가비지 컬렉터를 가진다.



-

진저브레드까지 안드로이드의 가비지 컬렉션은 메모리가 회수되는 동안 앱 실행이 중지되는 순차적 실행이었다.

이는 UI rendering 의 일시적 멈춤으로 이어질 수 있었다.

허니컴부터 GC 는 앱 스레드를 중지하지 않고 자체 스레드에서 동시적으로 실행된다.



-

달빅 GC 는 mark & swap 이라는 일반적인 두 단계 메커니즘을 사용한다.

마크 단계는 객체 트리를 탐색하고 다른 객체에 의해 참조되지 않는 모든 미사용 객체를 mark 한다.

사용하지 않는 객체는 가비지 컬렉션의 대상이 되고, 스왑 단계에서 mark 된 객체들의 할당이 해제된다.





6.2. 스레드 관련 메모리 누수


** 6.2.1. 스레드 실행


-

수명이 긴 스레드는 메모리 누수( 여기서는 오랫동안 메모리를 점유하고 있는 것도 누수의 일종으로 친다. ) 의 원인이 될 수 있다.



-

내부 클래스는 암시적으로 외부 클래스에 대한 참조를 가진다.

따라서 내부 클래스로 정의된 스레드는 스레드가 실행하는 동안 GC을 위해 마크되지 않는 외부 클래스에 대한 참조를 유지한다.


지역 클래스 및 익명 내부 클래스로 정의된 스레드 역시 실행되는 동안 외부 클래스를 GC 루트에서 도달 가능하게 유지한다.



-

정적(static) 내부 클래스는 외부 객체 자체가 아닌 외부 객체 클래스(Class<?>)에 대한 참조를 유지한다.

결국 instance 에 대한 참조가 해제되면 GC 대상이 된다.



-

대부분의 프로그래머는 테스크(Runnable)에서 실행 환경(Thread)를 분리하고 싶어 한다.

만약 새로운 Runnable 을 내부 클래스로 생성하면 생성된 Runnable 이 정적 내부 클래스에 의해 실행되는 경우에도 실행 중에 외부 클래스에 대한 참조를 유지한다.


이 case 를 혼동하지 않고 생각해야 한다.



-

안드로이드에서 누수에 대한 근본적인 이유는 구성요소, 객체, 스레드 사이의 생명주기가 불일치하기 때문이다.



-

Activity 의 onDestroy() 콜백은 다음과 같은 여러 가지 이유로 발생 할 수 있다.


사용자가 뒤로 가기 버튼을 누른다.

액티비티가 명시적으로 finish() 를 호출한다.

시스템이, 액티비티를 실행하는 앱 프로세스가 시스템 자원을 내주기 위해 종료될 수 있도록 결정했다.

설정 변경(configuration change)이 일어난다. 예를 들면 기기가 회전한 경우 재생성 할 때.



-

설정 변경과 사용자 탐색은 Activity 객체를 참조로 가진 많은 동시 스레드를 만들 수 있기 때문에 자동으로 시작된 스레드는 사용자가 시작한 스레드보다 더 높은 메모리 누수 위험을 가진다.




** 6.2.2. 스레드 통신


-

메시지는 메시지 큐에서 대기하거나 스레드에서 실행되는 동안 GC의 대상이 될 수 없다.

또한 메시지에 의해 모든 암시적 및 명시적 참조가 있는 핸들러와 Object, Runnable 역시 GC 대상이 아니다.



-

public class Outer{

    Handler mHandler = new Handler(){

        public void handleMessage(Message msg){

            // 메시지 처리, 여기서 이미 Outer 를 참조한다.

        }

    };


    public void doSend(){

        Message msg = mHandler.obtainMessage();

        msg.obj = new SampleObject();

        mHandler.sendMessageDeplayed(msg, 60 * 1000);

    }

}


위의 코드는 메시지가 handler 에 의해 처리될 때까지, SampleObject, Handler & Outer 에 대한 참조를 유지한다.





6.3. 메모리 누수 방지


-

메모리 누수를 방지하려면 다음의 방법들을 써야 한다.

    1. static inner class 사용 : outer 를 참조할 필요가 없다면 이 방법이 좋다.

    2. 약한 참조 사용 : outer 를 참조할 필요가 있을 때, outer 를 WeakReference 로 가지고 있으면 좋다.

    3. 작업자 스레드 실행 중지

    4. 작업자 스레드 유지 : 플랫폼에 따라 기존 액티비티에서 새로운 액티비티로 스레드를 이전시킬 수 있다.

    5. 메시지 큐 정리 : removeCallbacks, removMessages 등을 통해 제거할 수 있다.






반응형

댓글