10. 궁금했던 것들 4편(2) - Android Memory Leak

don9wan·2021년 11월 26일
0

반성 식탁

목록 보기
10/14
post-thumbnail
post-custom-banner

현재 앱에서 Memory Leak이 발생하는 상황이나 기준에 대해 알기 위해 궁금했던 것들 4편(1) - Garbage Collection에서 JVM Garbage Collection에 대해 다뤘다. 필자는 왜 GC를 다뤘을까?

우선, 불필요한 메모리가 해제되지 않아 성능 저하가 발생하는 것이 Memory Leak이다. 그리고 JVM 상에서 불필요한 메모리를 해제해주는 녀석이 GC(Garbage Collector)이다.

  1. 그렇다면, 개발자 입장에서 GC가 수행될 거라고 예상한 지점에서
  2. 정작 GC가 수행되지 않으면
  3. 메모리 누수가 발생하는 것이 아닐까?
  4. 라는 생각에서 출발했기 때문이다.

그렇다면 이번 글에서 다룰 내용은 '안드로이드에서의 GC 수행'에 관한 내용이다. 어떤 시점에서 GC가 수행하지 않아, 불필요한 객체가 메모리 상에 남아 있게 되는지에 대해 알아볼 것 같다.

Android GC

일단 공부를 하면서 생각을 해봤는데, 위의 'GC가 수행되지 않는다'라는 표현엔 오류가 있지 않나 싶다. 해당 표현은 애초에 GC가 정상적으로 동작하지 않는 상황이고, 이는 말 그대로 프로세스 내지 시스템 그 자체의 오류를 의미하기 때문이다. 필자의 'Android GC가 수행되지 않는 상황'이란 표현은 사실 아래의 표현이 정확할 것이다. 맞나?

GC에 의해 자동으로 해제될 것이라 생각했던 객체 인스턴스를 수동으로 해제해주지 않은 상황

이유를 들겠다. 필자는 지금까지 안드로이드 개발 및 공부를 진행해오면서 Memory Leak 가능성에 대한 내용을 직/간접적으로 접해왔다. 이러한 가능성이 있을 때 마다 위의 경우에 해당했다. 지금까지 접해온 'Android Memory Leak이 발생할 수 있는 상황'들은 다음과 같다.

Android Context - Application Context

Application Context는 기본적으로 Application LifeCycle에 종속되어 있는 싱글턴 인스턴스(인스턴스가 오직 1개만 생성되는 객체)이다. 따라서 현재 액티비티의 LifeCycle이 종료(destroy) 되더라도 GC에 의해 자동 해제되지 않는다. 이러한 특성 때문에 액티비티 수준이 아닌 애플리케이션 수준에서 생성한 싱글턴 오브젝트가 Context를 필요로 하는 경우 Application Context를 사용해야 한다. 싱글턴 인스턴스에서 Activity Context를 사용하면 어떻게 될까?

싱글턴(액티비티 컨텍스트)
싱글턴 -> 액티비티 컨텍스트 : 누수, Reachable

  1. 싱글턴 인스턴스에 액티비티 컨텍스트를 전달한다.
  2. 여기서부터 싱글턴 인스턴스는 액티비티 컨텍스트를 참조한다.
  3. 화면이 표시되지 않는다.
  4. 싱글턴 인스턴스는 이미 사용되지도 않는, 종료된 액티비티 컨텍스트를 여전히 참조하고 있다. GC는 액티비티 컨텍스트를 Reachable하다고 판단한다.
    • 싱글턴 인스턴스의 특성으로 인해 발생한다.
  5. 참조를 기준으로 판단하는 GC는 사용되지도 않는 액티비티 컨텍스트의 메모리를 해제하지 않는다.
  6. 액티비티 컨텍스트 인스턴스로부터 메모리 누수가 발생한다.
  7. 따라서 싱글턴 인스턴스의 경우 애플리케이션 컨텍스트를 전달해주는 것이 바람직하다.

QnA

Q1 : "싱글턴 인스턴스에 Activity Context를 전달한 경우, 액티비티의 화면이 표시되지 않는 순간에도 해당 인스턴스가 액티비티를 참조하며, 액티비티의 메모리 누수가 발생할 수 있다"라는 내용을 읽었는데요. 이미 Activity Context를 전달해버린 이상, 해당 인스턴스는 액티비티의 수명주기를 따르게 되면서, 문제가 발생하지 않는 것 아닌가요?
A1 : 싱글턴의 인스턴스에 들어가고 액티비티가 종료되면 이제는 사용하지 않은 액티비티 이기때문에 메모리에 회수가 되어야 겠죠! 물론 메모리에 회수 되려면 해당 액티비티에 대해 참조가 모두 끊어져야하구요.
근데 사용하지도 않는 이미 종료된 액티비티 컨텍스트가 싱글턴 인스턴스에서 사용하는 것처럼 참조가 남아 있게 되어 GC 입장에서는 알 방법이 없기 때문에 해제 하지 못하고 메모리릭으로 이어지게 됩니다.
Q2 : 싱글턴 인스턴스의 특성 때문에 이런 일이 발생한건가요? "싱글턴 인스턴스가 다른 곳에서 사용되는, 즉 참조되는 상황(코드)들이 남아 있게 되어서 GC가 인지하지 못한다." 라는 말씀으로 이해해도 될까요?
A2 : 싱글턴이 회수되면 액티비티 컨텍스트의 레퍼런스 카운트가 0 이 되니 GC 에서 해제할 수 있겠지만 싱글턴 특성으로 인해 해제 되기 어렵기 때문에 특정 조건에서 null 을 명시적으로 주던가, weakreference 를 사용하면 될 것 같아요 베스트를 액티비티 컨텍스트를 직접 사용할 일이 없도록 풀어내면 좋구요.


궁금했던 것들 2편 - 바인딩 클래스와 생명 주기 - Fragment

바인딩 클래스 인스턴스는 프래그먼트의 onDestroyView 콜백 메서드 내에서 null처리를 해줘야했다. 이를 하지 않으면 fragment replace 시 해당 인스턴스가 남아 메모리 누수가 발생할 수 있다. 프래그먼트가 destroy되면 GC가 인스턴스들을 모두 메모리 해제해줘야 하지 않나? 이상함을 감지했다. 나는 곧장 "fragemnt replace memory leak"에 대해 알아보기 시작했다.

알아 본 결과, 새로운 것을 알게 됐다. fragment replace 시, 해당 프래그먼트에서 onDestroyView() 콜백까지만 호출되고 onDestory() 콜백은 호출되지 않는다. 즉, 프래그먼트의 View만 destory(파괴)되었다는 것이다. 해당 프래그먼트의 인스턴스들은 살아 있다. 그리고 프래그먼트는 정리되지 않았던 바인딩 클래스 인스턴스를 여전히 참조하고 있다. 바인딩 클래스 인스턴스는 replace됐으므로 더 이상 사용될 필요가 없는데 말이다. 따라서 GC는 바인딩 클래스 인스턴스를 Reachable하다고 판단한다. GC에 의해 메모리 해제가 되지 않고 바인딩 클래스 인스턴스로부터 메모리 누수가 발생하는 것이다.

프래그먼트(바인딩클래스)
프래그먼트 -> 바인딩클래스 : 누수, Reachable

  1. _binding 변수에 바인딩 클래스 인스턴스를 생성 및 할당한다.
  2. 여기서부터 프래그먼트는 바인딩 클래스 인스턴스를 참조한다.
  3. fragment replace가 발생한다.
  4. destroy되지 않은 프래그먼트는 더 이상 사용되지 않는 바인딩 클래스 인스턴스를 여전히 참조하고 있다. GC는 바인딩 클래스 인스턴스를 Reachable하다고 판단한다.
  5. 참조를 기준으로 판단하는 GC는 더 이상 사용되지도 않는 바인딩 클래스 인스턴스의 메모리를 해제하지 않는다.
  6. 바인딩 클래스 인스턴스로부터 메모리 누수가 발생한다.
  7. 결국 Fragment가 View보다 오래 살아남기 때문에 이러한 메모리 누수가 발생하는 것이었다.
  8. 따라서 바인딩 클래스 인스턴스의 경우 프래그먼트의 onDestroyView()에서 null값으로 초기화해주는 것이 바람직하다.
  9. 안드로이드 공식 문서가 제공하는 null 처리 코드가 보기 깔끔하지 않다면, 괜찮은 대안 코드도 있다. - 바인딩 클래스 인스턴스를 깔끔하게 처리하기

결론

  1. 특정 객체가 더 이상 사용되지 않음에도 불구하고 참조되어 있는가를 고려해야겠다. '참조'는 GC가 Reachable 객체를 판단하는 기준이기 때문이다.
  2. 따라서 해당 객체의 사용 시기(스코프 범위)를 인지하며 개발을 해야겠다.
  3. 이어서, 개발을 진행함에 있어 Context가 필요해질 때가 많은데, 신중할 필요가 있겠다.
  4. 그리고 프래그먼트 lifeCycle과 같이 특수한 상황 또한 메모리 누수의 원인이 된다는 것을 알았다.

결국은 또 같은 답이다. 아는 것이 힘..

profile
한 눈에 보기 : https://velog.io/@dongwan999/LIST
post-custom-banner

0개의 댓글