[JavaScript] 가비지 컬렉션(Garbage Collection)

Mandy·2023년 1월 23일
1
post-thumbnail

✅배경지식 Check!
⚠ 간단한 js 코드를 읽고 이해할 수 있어야 함.
⚠ 원시 타입과 참조 타입을 알아야 함.


가비지 컬렉션(garbage collection), 이름 그대로 쓰레기를 수집하는 것이다.

우리 일상생활에서도 쓰레기를 버리고 수거해가는 작업은 매우 매우 중요하다!

쓰레기를 제때 치우지 않게 되면 그 장소는 악취와 병균의 온상이 될 것이다.

이와 마찬가지로, 자바스크립트에서도 쓸모가 없어진 값에 대해서 처리를 해야 한다.

그렇지 않으면 그것으로 인해 문제가 생길 수 있기 때문이다.

자바스크립트 엔진이 쓸모 없는 값에 대해 어떻게 처리하는지, 즉 가비지 컬렉터의 동작과 메모리 관리에 대해 알아보자.

1. 메모리 관리의 필요성

가비지 컬렉션을 알기에 앞서, C나 C++같은 저수준 언어가 메모리 관리를 하는 방법을 알면 좋다.

모르더라도 상관없다, 지금 설명할테니!

C에서는 malloc(), calloc() 등으로 메모리를 할당하고 free()로 메모리를 해제한다.
C++에서도 new와 delete라는 연산자로 메모리를 할당과 해제한다.

위와 같은 저수준 언어들은 개발자가 일일이 메모리의 동적 할당과 해제를 시켜줘야 하는 번거로움이 존재한다.

반대로 생각하면 직접 메모리 할당과 해제가 가능하다는 말이기도 하다.

그런데 우리는 자바스크립트에서 직접 메모리를 할당하고 해제 해본 적이 없을 것이다.

메모리 할당의 경우 자바스크립트는 변수 선언 시 메모리를 자동으로 할당해주기 때문이다.

원시 타입의 값과 참조 타입의 주소값은 V8엔진 내부 콜스택 영역에, 참조 타입의 값은 힙 영역에 저장된다.
변수 식별자는 콜스택 영역에 있는 실행 컨텍스트의 Lexical Environment에 저장된다.

메모리 해제의 경우 자바스크립트 엔진 V8의 가비지 컬렉션(GC)에 의해 해제된다.

그렇기 때문에 우리는 자바스크립트 개발을 하면서 단 한번도 직접 메모리의 할당과 해제에 관여해본적이 없는것이다!

그런데 메모리는 왜 할당하고서 해제해주는 것일까? 변수를 사용하기 위해 할당하는것은 이해할 수 있지만 해제는 반드시 필요한 것일까?

메모리 누수 : 컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상

때문이다.
메모리 누수가 지속될 경우 프로그램은 점점 느려지고 버벅대고 만다.(RIP)

이러한 현상을 막기 위해 메모리의 해제가 필요한 것이며 메모리 해제를 해주는 가비지 컬렉션에 대해 알아야 하는 것이다!

2. V8 엔진의 메모리 구조

프로그램이 실행되면 V8 프로세스에서 Resident Set이라는 이름의 메모리를 할당해준다.
이 안에는 스택 영역과 힙 메모리 영역이 있다. 이 중에서 스택 영역은 함수 호출이 끝나면 OS에 의해 정리가 되지만, 힙 메모리 영역은 그렇지 않다.

메모리 누수가 발생할 가능성이 있는 부분이 바로 이 힙 메모리 영역이다.

힙 메모리는 동적 데이터가 저장되는 영역이자 메모리 영역중 가장 큰 블록이고 가비지 컬렉션(GC)가 발생하는 곳이다.

하지만 힙 메모리의 모든 영역에서 가비지 컬렉션을 수행하는것이 아니라 그림에 표시된 New Space와 Old Space에서만 수행된다.

New Space : 새로 만들어진 모든 객체가 저장되는 곳이다.
Old Space : New Space에서 마이너 GC가 두 번 발생할 동안 살아남은 객체들이 이동하는 곳이다. 이 영역에서는 메이저 GC가 관리한다.

메이저... 마이너.. 뭐요?

마이너 GC와 메이저 GC는 서로 다른 방식으로 동작하는 가비지 컬렉터이다. 이에 대해 알아보기에 앞서 객체의 이동에 대해 알아보자.

방금 설명한 New Space는 2개의 Semi Space로 나뉘어져있다.
새로 만든 모든 객체들은 New Space에 들어간다고 했으니, 객체는 기본적으로 New Space에 있음이 확실하다.
그런데 이중에서도 첫번째 Semi Space에 할당된다. 그리고 마이너 GC로부터 한 번 살아남으면(메모리 해제되지 않으면) 그림에서 보듯이 두 번째 Semi Space로 이동하게 된다.

그리고 여기서 또 마이너 GC로부터 생존하면 그때서야 Old Space로 이동하게 되는 것이다.

3. 가비지 컬렉터(GC)

The Generational Hypothesis 가설은 대부분 새로운 객체가 오래된 객체보다 쓸모 없어질 가능성이 높다는 가설이다.

이유는 아까 말한 객체의 이동을 통해 납득할 수 있다.
오래된 객체는 벌써 3번이나 살아남은 전력이 있다. 하지만 새로운 객체는 아직 GC에 의해 살아남을수 있을지 미확정인 상태이다.

그렇기 때문에 오래된 객체가 새로운 객체보다 쓸모없어질 가능성이 낮으므로 이 둘을 분리해서 서로 다르게 동작하는 두 종류의 GC가 관리하는 것이다.

* 마이너 GC(Scavanger)
New Space에서 쓸모 없는 객체에 대한 메모리 해제를 수행하며, mark-and-sweep 알고리즘에 의해 살려둘 객체를 정한다.

mark-and-sweep 알고리즘 은 가비지 컬렉션의 기본 알고리즘이며, 기본적으로 아래 단계에 따른다.

  1. 가비지 컬렉터가 루트(root) 정보를 수집하고 이것을 mark 한다.
  1. 루트가 참조하고 있는 모든 객체를 방문하고 방문한 곳을 전부 mark 한다.
  1. mark 된 모든 객체들이 참조하는 객체도 방문해서 mark한다. 한번 mark된 객체는 재방문 하지 않는다.

  2. 루트에서 도달 가능한 모든 객체에 방문할 때까지 1 ~ 3을 반복한다.

  3. mark 되지 않은 모든 객체를 메모리에서 삭제한다.

위 과정을 그림 한장으로 표현하자면 다음과 같다.

mark되지 않은 즉, 루트로부터 도달할 수 없는 객체들은 삭제하는 것이다.

마이너 GC에서 살아남은 객체들이 어떻게 이동되는지에 대해서도 알아보자.

마이너 GC로부터 살아남은 객체는 항상 자신이 머무른 영역 외의 영역으로 대피한다. 그렇기 때문에, 하나의 Semi Space가 항상 객체가 머무는 영역이면 다른 하나의 Semi Space는 비어있는 영역이 된다.
객체가 머무는 Semi Space 영역을 From Space, 비어있는 Semi Space 영역을 To Space 라고 부른다.

살아남은 객체들이 From Space에서 To Space로 이동할 때 연속적인 메모리로 이동하기 때문에 메모리 단편화를 주기적으로 방지해주기도 한다.
그리고 이동한 객체는 새로운 메모리 주소값으로 포인터가 갱신된다.

생존 객체들의 대피가 완료되면, From Space에 있던 쓸모없는 객체들을 삭제하고 기존 From Space를 To Space로, To Space를 From Space로 바꾼다.

아까 말했다시피 객체가 머무는 Semi Space 영역을 From Space, 비어있는 Semi Space 영역을 To Space 라고 부른다.
그러므로 아까 To Space 였던 영역이 이제는 From Space가 되고 아까 From Space 였던 영역은 To Space가 된것이다.

현재 생존한 초록색 객체들이 앞으로 1번 더 생존한다면 To Space가 아닌 Old Space로 이동하게 된다.



* 메이저 GC
이번엔, Old Space에 있는 객체를 관리하는 메이저 GC에 대해 알아보자.
메이저 GC는 Mark-Sweep-Compact 알고리즘Tri-color 마킹 시스템을 사용하여 처리된다.

둘의 처리 방식이 다른 이유는 New Space의 객체는 크기가 작기 때문에 그에 적합한 스캐밴저 알고리즘에 따라 마이너 GC가 동작하지만, Old Space는 크기가 큰 메모리이기 때문에 마이너 GC의 동작이 적합하지 않기 때문이다.

메이저 GC는 세 단계로 진행된다.

  1. 마킹(Marking) : 어떤 객체가 쓸모없어진 객체인지 알아내기 위한 단계다. (white, gray, black으로 마킹한다.)

  2. 스위핑(Sweeping): 1 이후 여전히 white인 객체의 메모리 주소를 기록한다. 이 공간은 이제 사용가능 목록에 표시되며 다른 객체 저장시에도 사용될 수 있다.

  3. 압축(Compacting): 2 이후 메모리 단편화를 줄이고 새 객체에 대한 메모리 할당 성능을 증가시킨다.



* Orinoco
마이너 GC와 메이저 GC가 동작할 때 프로그램이 일시적으로 멈출 수 있는데, 이것을 stop-the-world라고 한다.
이러한 현상이 지속되면 프로그램이 버벅대고 느려지게 될 것이다.
그때문에 V8에서는 Orinoco 프로젝트를 통해 GC를 발전시켰다. 아래는 Orinoco 프로젝트에서 사용한 기술들이다.

Parallel: 메인 쓰레드에서 혼자 하던 일을 헬퍼 쓰레드들과 나눠 하는것으로 쓰레드 간 동기화 문제가 생겨 오버헤드가 발생하지만 stop-the-world 시간은 크게 줄어든다.

Incremental: 메인 쓰레드가 적은 양의 작업을 간헐적으로 처리한다. GC에 투자되는 시간이 적게 간헐적으로 쪼개지므로 UX가 개선된다.

Concurrent: 메인 쓰레드는 GC를 하지않고 헬퍼 쓰레드들이 수행한다. 기술적 구현이 어려우나 stop-the-wolrd 시간이 전혀 없다.





이상으로 가비지 컬렉션에 대한 내용을 알아봤다.

많은 내용들이 있었지만, 가비지 컬렉션의 동작에 대해 알게되어 유익한 시간이였다!
여태 몰랐지만, 가비지 컬렉션을 알고나서 자바스크립트에서 자동 메모리 할당과 가비지 컬렉션의 컬래버레이션덕에 내가 해야될 일이 줄어드는것 같다고 느꼈다.😁

참고한 자료들에 더욱 자세하고 좋은 내용들이 많으니 본론이 부족하다 느꼈다면 한 번 살펴보는것을 강력 추천한다!

-끝



참고한 자료
https://v8.dev/blog/trash-talk (v8 블로그)
https://front-end-news.tistory.com/entry/Trash-talk-the-Orinoco-garbage-collector-%ED%95%9C%EA%B8%80(v8 블로그 번역)
https://ko.javascript.info/garbage-collection (자바스크립트 튜토리얼)
https://fe-developers.kakaoent.com/2022/220519-garbage-collection/ (Kakao 엔터테인먼트 FE 기술블로그)
https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_Management (MDN)
https://velog.io/@leehyunho2001/가비지-컬렉션-모르시는분 (velog)
https://ui.toast.com/weekly-pick/ko_20200228 (TOAST UI)

profile
즐코 행코 하세용

0개의 댓글