Android 에서 흔히 볼 수 있 는 메모리 유출 과 해결 방안 분석

머리말
현재 자바 쓰레기 회수 주류 알고리즘 은 가상 컴퓨터 가 GC Roots Tracing 알고리즘 을 사용 하고 있다.알고리즘 의 기본 적 인 사고방식 은 일련의 GC Roots(GC 루트 노드)라 는 대상 을 시작 점 으로 삼 아 이 노드 에서 아래로 검색 하고 지나 간 경 로 를 검색 하 는 것 이다.한 대상 에서 GC Roots 까지 인용 체인 이 연결 되 어 있 지 않 을 때(그림 은 GC Roots 에서 이 대상 까지 도달 할 수 없 음)이 대상 이 사용 할 수 없다 는 것 을 증명 한다.
접근 성에 관 한 대상 은 바로 GC Roots 와 연결 도 를 구성 할 수 있 는 대상 이다.다음 과 같다.

루트 검색 알고리즘 의 기본 적 인 사고방식 은 일련의'GC Roots'라 는 대상 을 시작 점 으로 삼 아 이 노드 에서 아래로 검색 하 는 것 이다.검색 한 경 로 를 인용 체인(Reference Chain)이 라 고 하 는데 한 대상 이 GC Roots 에 인용 체인 이 연결 되 어 있 지 않 을 때 이 대상 이 사용 할 수 없다 는 것 을 증명 한다.
위의 그림 에서 reference 1,reference 2,reference 3 는 모두 GC Roots 로 알 수 있다.
reference 1->대상 인 스 턴 스 1;
reference 2->대상 인 스 턴 스 2;
reference 3->대상 인 스 턴 스 4;
reference 3->대상 인 스 턴 스 4->대상 인 스 턴 스 6;
대상 인 스 턴 스 1,2,4,6 은 모두 GC Roots 접근 성,즉 생존 대상 이 므 로 GC 에 회수 되 어 서 는 안 되 는 대상 임 을 알 수 있다.
한편,대상 인 스 턴 스 3,5 가 직접적 으로 연결 되 지만 그 어떠한 GC Roots 도 연결 되 지 않 았 다.이것 이 바로 GC Roots 가 도달 할 수 없 는 대상 이다.이것 이 바로 GC 가 회수 해 야 할 쓰레기 대상 이다.
GC 를 알 게 된 후 안 드 로 이 드 의 메모리 유출 상황 을 알 아 보기 시작 했다.
2.안 드 로 이 드 메모리 유출 장면
흔히 볼 수 있 는 메모리 유출 장면 과 이에 대응 하 는 복구 방법 을 상세히 소개 한다.
2.1 비 정적 내부 클래스 의 정적 인 스 턴 스
예 를 들 어 우 리 는 Activity 내부 에서 내부 클래스 InnerClass 를 정의 하 는 동시에 정적 변수 inner 를 정의 하고 값 을 부여 합 니 다.만약 당신 이 onDestory 에 있 을 때 inner 를 null 로 설정 하지 않 았 다 고 가정 합 니 다.그러면 메모리 유출 이 일어 날 겁 니 다.정적 변 수 는 내부 클래스 의 인 스 턴 스 를 가지 고 있 기 때문에 내부 클래스 는 외부 클래스 에 인용 되 어 Activity 가 방출 되 지 않 습 니 다.

private static Object inner;
void createInnerClass() {
    class InnerClass {
    } 
    inner = new InnerClass();
}
View icButton = findViewById(R.id.ic_button);
    icButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createInnerClass();
        nextActivity();
    }
});
생명주기 가 끝 날 때 필요 하지 않 은 정적 변 수 를 null 로 설정 하 는 것 을 기억 하 십시오.
2.2 다 중 스 레 드 와 관련 된 익명 내부 클래스/비정 상 내부 클래스
비 정적 내부 클래스 와 마찬가지 로 익명 내부 클래스 도 외부 클래스 의 인 스 턴 스 를 참조 합 니 다.다 중 스 레 드 와 관련 된 클래스 는 AsyncTask 클래스,Thread 클래스,Runnable 인터페이스 클래스 등 이 있 습 니 다.익명 내부 클래스 가 시간 이 걸 리 면 작 동 합 니 다.
메모리 유출 이 발생 할 수 있 습 니 다.AsyncTask 의 익명 내부 클래스 를 예 로 들 면 다음 과 같 습 니 다.

void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }.execute();
}

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        startAsyncTask();
        nextActivity();
    }
});
비동기 작업 이 배경 에서 시간 이 걸 리 는 작업 을 수행 하 는 동안 Activity 는 불행 하 게 도 소각 되 었 습 니 다(예 를 들 어 사용자 종료,시스템 회수).이 AsyncTask 가 가지 고 있 는 Activity 인 스 턴 스 는 쓰레기 회수 기 에 의 해 회수 되 지 않 고 비동기 작업 이 끝 날 때 까지 회수 되 지 않 습 니 다.
해결 방법 은 AsyncTask 를 계승 하여 정적 내부 클래스 를 새로 만 드 는 것 입 니 다.정적 내부 클래스 로 인 스 턴 스 를 만 들 면 외부 인 스 턴 스 에 대한 참조 가 존재 하지 않 습 니 다.
2.3,Handler 메모리 유출
마찬가지 로 Handler 의 message 는 메시지 대기 열MessageQueue에 전 달 됩 니 다.Message메시지 가 처리 되 기 전에 handler 의 인 스 턴 스 도 회수 되 지 않 습 니 다.handler 인 스 턴 스 가 정적 이지 않 으 면 인 용 된 activity 나 service 가 되 돌 릴 수 없 기 때문에 메모리 누 출 이 발생 합 니 다.

void createHandler() {
    new Handler() {
        @Override public void handleMessage(Message message) {
            super.handleMessage(message);
        }
    }.sendMessageDelayed(Message.obtain(), 60000);
}


View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createHandler();
        nextActivity();
    }
});
상기 문제 에 대해 두 가지 해결 방법 이 있 는데 하 나 는 정적 인 handler 내부 류 를 사용 하고 가지 고 있 는 대상 은 모두 약 한 인용 형식 으로 바 꾸 어 인용 하 는 것 이다.또 하 나 는 activity 를 소각 할 때 보 낸 메 시 지 를 제거 하 는 것 이다.

myHandler.removeCallbackAndMessages(null);
이런 문 제 는 핸들 러 의 메시지 가 다 처리 되 지 않 을 수도 있다 는 점 이다.
또 하 나 는 View\#post 를 직접 사용 하지 않 는 것 이 좋 습 니 다.사용 하려 면 view 가 window 에 첨부 되 어 있 는 지 확인 하 세 요.
2.4 정적 활동 또는 View
클래스 에서 정적Activity변 수 를 정의 하고 현재 실행 중인Activity인 스 턴 스 를 이 정적 변수 에 할당 합 니 다.
이 정적 변 수 는Activity수명 주기 가 끝 난 후에 비 워 지지 않 으 면 메모리 가 누 출 됩 니 다.static 변 수 는 이 애플 리 케 이 션 의 생명 주 기 를 관통 하기 때문에 누 출 된Activity은 애플 리 케 이 션 프로 세 스에 계속 존재 하고 쓰레기 회수 기 에 의 해 회수 되 지 않 습 니 다.

static Activity activity;
void setStaticActivity() {
    activity = this;
}

View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
    setStaticActivity();
    nextActivity();
    }
});
회수 할 수 있 도록 사용 할 필요 가 없 을 때 null 작업 을 해 야 합 니 다.예 를 들 어 현재 activity 를 없 앨 때.
특수 상황:하나의 View 가 초기 화 되 어 많은 자원 을 소모 하고 하나의Activity수명 주기 내 에 변 하지 않 으 면 static 로 바 꾸 어 보기 트 리 에 불 러 올 수 있 습 니 다(View Hierachy).이렇게Activity가 소각 되 었 을 때 자원 을 방출 해 야 합 니 다.

static view;
void setStaticView() {
    view = findViewById(R.id.sv_button);
}

View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
    setStaticView();
    nextActivity();
    }
});
마찬가지 로 메모리 유출 문 제 를 해결 하기 위해 Activity 가 소각 할 때 이 static view 를 null 로 설정 하면 되 지만 이 static view 를 사용 하 는 방법 은 권장 하지 않 습 니 다.
2.5,Eventbus 등 등록 감청 으로 인 한 메모리 유출
많은 학우 들 이 프로젝트 에 Eventbus 를 사용 할 것 이 라 고 믿 습 니 다.경험 이 없 는 일부 학우 들 에 게 사용 할 때 자주 문제 가 발생 한다.예 를 들 어 onCreate 를 할 때 등록 을 하고 반 등록 을 잊 어 버 리 거나 onStop 을 할 때 반 등록 을 하면 Eventbus 의 메모리 가 유출 된다.

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    EventBus.getDefault().register(this);//    onCreate()     
}

@Override
public void onDestroy() {
    EventBus.getDefault().unregister(this);//    onDestory()     
    super.onDestroy();
}
등록 과 반 등록(등록 취소)은 대응 합 니 다.추가 해 야 합 니 다.그렇지 않 으 면 구성 요소 의 메모리 누 출 을 일 으 킬 수 있 습 니 다.등록 할 때 구성 요 소 는 EventBus 내부 의 단일 대기 열 에 의 해 참조 되 었 기 때 문 입 니 다.
만약 당신 이 View 에 Eventbus 를 등록 했다 면,View 의 생명주기 인 onAttached ToWindow 와 onDetached FromWindow 에 등록 하고 반 등록 한 것 을 기억 하 세 요.
최근 에 제 동료 와 이 야 기 를 나 누 었 을 때 그들 은 이벤트 버스 로 인 한 메모리 유출 문 제 를 해결 하기 위해(이미 쌍 등록 과 반 등록 이 되 었 지만 메모리 유출 문제 가 존재 합 니 다)object 의 인 스 턴 스 를 만 들 려 고 합 니 다.이 를 통 해 등록 과 반 등록 을 하려 고 합 니 다.그러면 메모리 유출 이 발생 하 더 라 도 아주 작은 메모리 공간 만 차지 할 수 있 습 니 다.
2.6.단일 사례 로 인 한 메모리 유출
프로젝트 에는 항상 많은 사례 가 존재 한다.현재 Activity 인 스 턴 스 를 하나의 예 로 전달 하고 뭔 가 를 해 야 할 때 가 있 습 니 다.다음 코드 와 같이:

public class SingleInstance {
    private Context mContext;
    private static SingleInstance instance;
 
    private SingleInstance(Context context) {
        this.mContext = context;
    }
 
    public static SingleInstance getInstance(Context context) {
        if (instance == null) {
            instance = new SingleInstance(context);
        }
        return instance;
    }
}
상기 사례 에서 context 가 들 어 오 면 context 의 생명 시간 이 응용 되 는 생명 시간 과 같 습 니 다.메모리 유출 로 이 어 질 수 있 습 니 다.
이런 것 에 대해 세 가지 해결 방법 이 있다.
1.약 한 인용 방식 으로 인용 하여 회수 할 수 있 도록 확보한다.
2.대응 하 는 context 가 소 멸 될 때 null 을 설치 합 니 다.원래 의 생명 보다 길지 않도록 하기;
3.앱 context 를 사용 할 수 있 는 지 확인 하기;이렇게 하면 메모리 유출 문제 가 존재 하지 않 을 것 이다.
2.7 자원 대상 이 닫 히 지 않 아 메모리 누 출
자원 을 열 때 캐 시 를 사용 합 니 다.예 를 들 어 파일 자원 읽 기,데이터베이스 자원 열기,Bitmap 자원 사용 등 이다.우리 가 더 이상 사용 하지 않 을 때,캐 시 메모리 영역 을 제때에 회수 할 수 있 도록 그것들 을 닫 아야 한다.일부 대상 은 우리 가 닫 지 않 으 면 finalize()함수 에서 스스로 닫 습 니 다.그러나 이 는 GC 가 회수 할 때 까지 기 다 려 야 꺼 져 캐 시가 일정 기간 머 물 게 된다.우리 가 자원 을 자주 열 면 메모리 누 출 에 미 치 는 영향 이 비교적 뚜렷 하 다.
해결 방법:자원 을 제때에 닫 습 니 다.
2.8、WebView
서로 다른 안 드 로 이 드 버 전의 웹 뷰 에 차이 가 있 을 수 있 습 니 다.게다가 서로 다른 업 체 가 맞 춤 형 ROM 의 웹 뷰 차이 로 인해 웹 뷰 에 큰 호환성 문제 가 존재 합 니 다.weView 는 메모리 유출 문제 가 있 을 수 있 으 며,응용 프로그램 에서 한 번 만 사용 하면 메모리 가 풀 리 지 않 습 니 다.일반적인 방법 은 웹 뷰 를 위 한 프로 세 스 를 따로 열 고 AIDL 을 사용 하여 메 인 프로 세 스 와 통신 하 는 것 입 니 다.웹 뷰 프로 세 스 는 업무 의 수요 에 따라 적당 한 시기 에 소각 할 수 있 습 니 다.
이상 은 안 드 로 이 드 에서 흔히 볼 수 있 는 메모리 유출 과 솔 루 션 에 대한 상세 한 내용 을 분석 하 는 것 입 니 다.안 드 로 이 드 메모리 유출 과 솔 루 션 에 관 한 자 료 는 다른 관련 글 을 주목 하 세 요!

좋은 웹페이지 즐겨찾기