내용 보기

작성자

관리자 (IP : 106.247.248.10)

날짜

2023-01-04 10:20

제목

[Flutter] [스크랩] The Dart Garbage Collector(GC)


Dart Garbage Collector는 두 가지(단계)의 조합으로 이루어진다.

  • Young Space Scavenger
  • Parallel mark-sweep collector

위 두가지에 대해 알아보기 전에 어떤 방식으로 GC가 어떻게 작동하는지 알아보자.

Scheduling

GC는 앱과 UI 성능에 영향을 최소화하기 위해서 Flutter engine에게 hook들을 제공한다.

c.f) hook

engine에 의해 앱이 idle하면서 유저 상호작용(user interaction)이 없을 때 감지해 알려주는 것을 의미한다.

이로써 GC가 성능에 영향을 끼치지 않은채 수집 단계(collection phase)들을 진행할 수 있도록 기회의 창을 마련해준다. 또한, GC는 앱이 idle한 동안 sliding compaction을 수행할 수 있다. 이는 메모리 파편화(fragmentation)을 감소시키면서 메모리 오버헤드를 최소화해준다.

c.f) sliding

다음 position을 결정하는 것을 의미한다.

Young Space Scavenger

번역을 하자니 마땅한게 떠오르지 않았다.. 글을 읽다보니 짧은 생명주기를 갖는 객체들을 청소하기 위해 고안된 모델이니 짧은 공간 청소부(?) 정도로 이해하면 될 듯 하다!

Stateless Widget과 같이 짧은 생명 주기를 가진 객체들을 해치우기(clean up) 위해서 고안된 단계이다. Blocking 중 일때는 두번째 단계인 mark/sweep보다 빠르다. 그리고 스케줄링과 함께 결합되어 사용되면 앱을 실행할 때 볼 수 있는 정지된 상태(렉)들을 거의 없앨 수 있다.

다른 말로 객체들은 메모리에 연속적으로 적재(할당)된다. 그리고 객체들이 생성되면서 할당될 수 있는 메모리가 모두 꽉 찰 때까지 다음으로 사용가능한 빈 공간에 할당되게 된다. Dart는 bump pointer allocation을 사용해 새로운 공간을 빠르게 찾아 프로세스를 굉장히 빠르게 만든다.

c.f) bump pointer allocation

할당하는데 빠르지만 제약이 있는 방법이다. 객체를 할당할 때 여유 공간이 있는지 빠르게 확인하고 포인터를 객체의 사이즈에 따라 할당해주는 방법이다.

새로운 객체가 할당될 새로운 공간에는 Semi space 라고 불리는 곳에 저장되는데, 이는 두개의 공간으로 나뉜다.


활성화된 상태(Active space)와 비활성화된 상태(Inactive space)로 나뉘어 오직 한 쪽만 시기와 상관없이 사용할 수 있다. 새로운 객체들은 활성화된 공간에 저장되며 이 공간이 모두 찼을 때에는 살아있는 객체들은 죽은 객체들을 무시한채 활성화된 공간에서 비활성화된 공간으로 복사된다. 비활성화된 공간이 이제 활성화되면서 위 프로세스가 반복된다.

반복 프로세스

Active space와 Inactive space 중 한 쪽만 아무때나 상관없이 사용가능

  1. 새로운 객체들이 활성화된 공간에 저장된다.
  2. 모든 공간이 사용되었다면 살아있는 객체들은 비활성화된 공간으로 복사된다.
  3. 비활성화된 공간을 활성화시킨다.

그렇다면 죽은 객체는 어떻게 판단할까?
객체가 죽었는지 살았는지를 비교하기위해서는 collector는 스택(메모리)에 저장된 변수와 루트(root) 객체부터 그것이 무엇을 참조하고 있는지 살펴본다.

Flutter는 build시 Widget들이 트리 구조로 되어있다는 점과 reference를 확인할 때는 hashCode로 확인할 수 있다.

이 다음에 참조된 객체들을 옮겨준다. 비워진 객체들이 어디를 가리키는지(point to) 알아내면서 참조된 객체들을 옮겨준다. 살아있는 객체들이 모두 옮겨질때까지 아래 그림과 같이 진행된다.

죽은 객체들은 참조된 것이 없으니 남은 것이 더이상 없다.
살아있는 객체들은 머지않아 GC이벤트에 의해 복제된다.


Parallel Marking and Concurrent Sweeping

어떤 객체가 특정 생명 주기를 달성했을 때 해당 객체는 두번째 단계인 mark-sweep에 의해 새로운 메모리 공간으로 옮겨진다.

위 GC 기술(mark-sweep)은 두 가지 단계로 나뉘어진다. 먼저 객체 그래프가 탐색되면서 객체는 '아직 사용 중'으로 마킹(표시)된다. 그 다음은 전체 메모리가 스캔되고 마킹되지 않은 객체들은 재활용된다. 마지막으로 모든 플래그(그동안 마킹된)들은 삭제된다.

이러한 형태의 garbage colllection(쓰레기 수집)은 마킹 단계에서 차단된다. 그 어떤 메모리 변화는 일어날 수 없으며 UI 스레드는 차단된다(blocked). 이 수집(collection)은 Young Scavenger에 의해 처리되는 짧은 생명 주기를 가진 객체들과 같이 더 드물지만, 때로는 Dart 런타임 때 이러한 형태의 garbage collection을 하기 위해서 멈출 수도 있다. Flutter가 가진 collection 스케줄링 능력으로 봤을 때 이로 인한 영향(멈추고 수집하는 작업)은 최소화 되어야한다.

반드시 앱이 "거의 모든 객체들이 짧은 생명주기를 갖는 것 처럼" 약한 세대 가설(weak generational hypothesis)에 붙지 않도록 주의해야한다. 그렇지 않으면 위와 같은 collection이 더욱 빈번하게 일어날 것이다. Flutter의 Widget이 구현되는 방법을 보면 자주 일어날 상황은 아니지만 명심해야할 일이다.

Isolates

Dart isolate는 다른 isolate와 메모리를 공유하지 않고 스스로의 private heap(고유한 메모리)를 갖고 있다는 것을 알고 있을 필요가 있다. 각 isolate는 분리된 스레드 상에서 작동되며 GC 이벤트 또한 다른 isolate에 영향을 끼쳐서는 안된다. Isolate를 사용하는 것이 UI를 차단하고 중요한 프로세스를 off-load하는 것을 방지할 수 있다.

출처1

https://velog.io/@seunghwanly/%EC%99%9C-Flutter%EA%B0%80-Reactive-Programming%EC%97%90-%EC%A0%81%ED%95%A9%ED%95%9C%EC%A7%80#the-dart-garbage-collectorgc

출처2