모든 프로그래밍 언어에서 어려우면서도 반드시 알아야 되는 것 중 하나는 메모리 관리입니다. 컴퓨터의 자원은 한정되어 있고, 어플리케이션은 반드시 메모리를 할당합니다. 여러 인스턴스가 존재하면 그만큼 많이 할당합니다. 그렇기에 사용되지 않는 인스턴스는 메모리에서 제거할 필요가 있습니다. 만약 사용되지 않는데 메모리에 계속 남아있다면요?
이미지 출처 : https://www.nextree.io/memory-leak/
메모리는 할당되었으나 더 이상 참조되지 않는 데이터로 메모리가 낭비되는 현상을 메모리 누수(Memory Leak)라고 합니다. 이러한 메모리 낭비로 인해 앱의 퍼포먼스는 떨어질 수 있겠죠. 특히나, 사용자의 기기가 느리면 느릴 수록 그 현상은 더욱 두드러질 것입니다.
Flutter는 어떻게 메모리를 관리할까요? 오늘은 Flutter의 메모리 관리방식에 대해서 한번 알아보고자 합니다.
Flutter는 Dart를 기본 개발 언어로 사용하고 있습니다. 개발 말고도 실행역시 Dart로 실행됩니다. 기본적인 개발환경은 디버깅 모드, 출시를 위한 릴리즈 모드로 나눌 수 있습니다. 디버깅 모드와 릴리즈 모드는 모두 runtime을 포함합니다. 디버깅 모드는 그 외에도 JIT와 인터프리터(JIT는 안드로이드, 인터프리터는 iOS)를 포함하긴 하지만 말이죠.
이미지 출처 : https://medium.com/flutter/flutter-dont-fear-the-garbage-collector-d69b3ff1ca30
Dart의 runtime에 포함되어 있는 Garbage Collector는 메모리의 할당과 해제를 담당합니다. 힙 영역을 순회하면서 더이상 참조되지 않는 객체를 메모리 해제를 하죠. Flutter는 Dart의 Garbage Collector를 이용해서 메모리를 관리합니다.
Flutter는 규모가 큰 어플리케이션일수록 수없이 많은 위젯들을 생성합니다. 이 위젯들은 앱의 state가 계속 변경됨에 따라서 생성과 해제를 반복하게 됩니다. 절대로 메모리가 해제되지 않는 객체에 대한 참조를 생성하고 이 객체가 해제되고 다시 생성하는 것을 방지하는 것이 적절할 것 같지만, 실제로는 그렇지 않습니다.
그렇다면 Dart의 Garbage Collector에 대해서 한번 알아보겠습니다.
Dart는 두 단계의 Garbage Collector를 수행하게 됩니다. 하나는 Young Space Scavenger와 다른 하나는 Parallel Mark Sweep Collector입니다.
스케줄링
일단 Flutter Engine은 앱이 유휴하지만 사용자의 상호작용이 없는 것을 감지하면 경고를 날립니다. Garbage Collector는 Flutter Engine에 Hooks를 제공합니다. 이 과정을 통해서 Garbage Collection으로 인한 앱의 UI에 영향을 최소화하면서 Garbage Collection을 수행할 수 있습니다.
Young Space Scavenger
Young Space Scavenger는 Stateless Widget처럼 수명이 짧은 위젯을 위한 Garbage Collection 단계입니다. Parallel Mark Sweep Collector보다 훨씬 빠르게 Collection을 수행하며 스케줄링과 함께 실행하면 앱의 일시중지가 사실상 제거됩니다.
이미지 출처 : https://medium.com/flutter/flutter-dont-fear-the-garbage-collector-d69b3ff1ca30
객체는 기본적으로 메모리의 연속적인 공간에 할당됩니다. 할당된 공간이 모두 사용될 때까지는 다음 생성 가능 공간에 메모리를 할당합니다. Dart는 bump pointer allocation을 이용하여 빠르게 다음 공간을 찾을 수 있기 때문에 이 과정이 굉장히 빠릅니다.
새로운 객체가 할당되기 위해서 할당된 공간은 Semispaces로 불리우는 2개의 half space로 분리됩니다. 한개의 공간은 Active Space이고, 다른 공간은 Inactive Space입니다. 여기서 객체는 Active Space에 할당됩니다.
Active Space가 모두 할당되면, collection을 수행합니다. 그리고 참조되는 객체는 Inactive Space로 복사하고 나머지는 모두 해제합니다. 그리고 Inactive Space가 활성화됩니다. 이 과정을 계속 반복하게 됩니다.
참조되는 값을 알아내기 위해서 스택 변수같은 루트 객체로부터 참조를 검사하며 참조되는 객체를 찾아냅니다. 그리고 찾아낸 객체를 이동시키고, 이 객체가 참조하는 다른 객체를 검사하고 또 이동시킵니다. 이 과정을 모두 찾아낼 때까지 계속 수행하게 됩니다.
Parallel Mark Sweep Collector
객체가 특정 수명에 도달하면 Parallel Mark Sweep Collector가 관리하는 메모리 공간으로 승격됩니다.
이 단계에서는 두가지 단계로 Garbage Collection을 수행합니다. 첫번째로 객체 그래프를 탐색하면서 아직 사용중인 객체를 표시합니다. 두번째로 전체 메모리를 스캔하면서 표시되지 않은 객체들을 모두 Garbage Collection을 수행합니다. 그리고 모든 flag를 제거합니다.
마킹단계에서 앱이 Block되기 때문에 UI 스레드가 차단됩니다. 즉, 앱이 멈추게 되겠죠. 대부분 Young Space Scavenger 단계에서 Garbage Collection을 처리하게 되지만, 수명이 긴 객체들이 많을 수록 Parallel Mark Sweep Collector가 수행될 수 있다는 것을 주의해야 합니다.
Flutter: Don’t Fear the Garbage Collector
플러터의 가비지 컬렉터
메모리 누수의 개념과 방지방법