Don’t worry about failures

Javascript의 가비지컬렉터 본문

Type.Java.Script

Javascript의 가비지컬렉터

허흥 2024. 3. 3. 13:22
728x90

C와 같은 저수준의 언어가 아니면 언어들은 보통 가비지 컬렉터가 존재하며, 불필요한 메모리를 정리한다.

 

자바스크립트의 v8엔진 기준으로 가비지 컬렉터에 대해 알아보자.

 

메모리 할당 구조.

변수는 원시 변수와 참조 변수로 나누어져있다.

이 두 타입의 변수는 저장되는 방식이 약간 다르다. 원시변수는 단순하게 콜스택에 바로 저장이 되지만, 참조 변수의 경우 메모리 힙에 실제 변수가 저장이 되고, 이에 대한 주소값을 콜스텍에 저장을 한다.

 

가비지컬렉터는 참조 변수 관리의 역할을 한다. 

즉, 참조 변수는 메모리에 저장되고 주소가 콜스텍에 저장이 된다고 했다. 여기서 콜스텍에 있는 데이터가 사라진다고 하면 메모리 힙에 있는 값을 따로 삭제하지 않으면 아무도 사용하지 않는 메모리가 되어버린다. 이렇게 됐을 때 가비지 컬렉터가 관계가 떨어져 버린 메모리를 삭제하는 역할을 한다. 

 

힙 메모리에  대해 더 자세히 봐보자

출처 : https://yceffort.kr/2020/11/v8-memory-management

힙 메모리에는 여러 공간으로 나누어져 있지만, 가비지컬렉터와 관계 있는 영역에 대해 살펴보면,

New Sapce(Young Generation) : 새로운 참조 변수 혹은 객체의 값이 저장되는 영역. 이 영역은 Semi space로 2개로 또 분리가 된다. 이는 Minor GC에서 관리가 된다.

Old space(Old Generation) : new Space에서 minor GC 사이클로부터 살아남은 객체들이 이동한다. 이 영역은 Pointer space, Data space로 분리가 된다. Point space는 다른 객체를 참조하는 객체, 즉 다른 객체에 대한 포인터를 가진 객체이다. Data space는 문자열, 실수 등의 데이터만을 가진 객체이다. 이는 Major GC에 의해 관리가 된다.

 

Minor GC에 의해 Young Generation에서 어떻게 동작되고 있는지 봐보자.

Minor GC는 Scavenger 통해 쓰레기를 수집한다. 

1. 새로운 객체가 할당될 때마다 New Space(semi space 중 한 곳)에 추가

2. New Space가 가득차면 스캐빈저 알고리즘 실행.

3. 스캐빈저는 New Space 영역에서 도달 가능한 모든 객체 식별.

4. 도달 가능하지 않은 객체는 가비지 표시, 이후 메모리 제거

5. 도달 가능한 영역 New Space의 semi space1 -> semi space2 이동. 

6. 이를 반복 후 old space로 이동

 

위와 같은 큰 루틴을 가지고 있다. 이 루틴에서 궁금한 점이 생길 수 있다. 언제 old space로 이동을 할까?

semi space1 -> semi space2로 이동하고 반복하면 다시 space2 -> space1로 이동하면 반복을 한다. 이러한 반복의 횟수를 bit-age라 하며, 이 bit-age가 promotion threshold가 되면 그때 old space로 이동하게 되는 것이다.

v8의 default promotion threshold의 값은 15이다.

 

Major GC에 의해 Old Generation에서 어떻게 동작되고 있는지 봐보자.

young generation에서 넘어온 데이터는 Marking-Sweeping-Compaction 알고리즘에 의해 관리가 된다. 

Marking

가비지 컬렉션 대상을 찾기 위한 단계. dfs로 순회하며 Tri-color(white, gray, black)로 마킹.

- white : GC가 탐색하지 않은 곳. 초기에는 모두 흰색.

- gray : 가비지 컬렉션의 스캔 작업이 시작될 때, 루트에서 시작하여 도달 할 수 있는 모든 객체는 회색으로 표시. 아직 스캔 완료는 되지 않은 상태.

- black : 스캔 완료.

검은색 객체를 기준으로 가비지 컬렉션 수행. 

 

Sweeping

가비지를 식별하고 제거하는 단계. 표시된 객체를 지외한 나머지 객체는 메모리에서 제거. 

 

Compact

메모리에 발생한느 조각화를 해소하기 위해 남아 있는 객체들을 압축. 메모리 낭비 축소.

 

이와 같이 더이상 사용되지 않은 객체는 GC에 의해 제거된다.

 

여기서 추가로 궁금한 점이 생길 수 있다. 왜 굳이  Young과 Old를 나눠서 사용할까?

GC설계자들은 어플리케이션을 분석했을 때 대부분의 객체가 수명이 짧다라는 것을 파악했다. 이러한 결과는 짧은 주기의 객체와 오래된 객체를 분리하여 관리를 해  성능을 높이고자한 것이다.

 

그럼 왜 분리를 하면 더 성능이 높아지는가?

오래된 객체의 경우 가비지 컬렉터를 통해 제거하기 위해 더 많은 리소스가 소요된다. 모든 데이터를 한 곳에 저장한다고 해보자. 매번 GC를 실행 시킬 때매다 모든 메모리를 확인해야한다. 그만큼 GC가 행동하는 시간은 오래걸리게 되고 성능에 있어 문제가 될 것이다.

이를 young과 old로 분리하게 되면, young은 비교적 짧은 수명의 객체들만 있기 때문에 자주 가비지 컬렉션을 발생시켜 제거하며 관리를 해고, old의 경우 오래된 객체를 담고 있기 때문에 young 보다는 적은 횟수로 가비지 컬렉션을 발생시킨다. 

이와 같이 메모리를 최대한 효율적으로 관리하며 성능을 향상시키기 위해 분리를 하여 관리 하는 것이다.

 

추가적으로 위와 같이 GC가 발생되면 프로그램은 잠시 멈추게 된다. 이를 stop-the-world라고 한다.

이를 위해 계속된 발전해오고 있다.

 

1. Parallel

출처 : https://v8.dev/blog/trash-talk

기존 메인 쓰레 드 혼자 하던 일을 helper쓰레드를 균등하게 나눠 일처리. 쓰레드 간의 동기화를 처리해야 해서 오버헤드가 생기지만 stop-the-world 시간 감소

 

2. Incremental

출처 : https://v8.dev/blog/trash-talk

메인 쓰레드가 적은 양의 작업을 간헐적으로 처리. 메인 쓰레드에서 가비지 컬렉션에 소요하는 시간이 분산.

 

3. concurrent

출처 : https://v8.dev/blog/trash-talk

메인 쓰레드는 가비지 컬렉션에 기여하지 않고, 헬퍼 쓰레드들이 수행. 기술적 구현 어려움. 메인 쓰레드의 stop-the-world 시간이 전혀 없음

728x90

'Type.Java.Script' 카테고리의 다른 글

babel, terser and SWC with Next.js (1)  (0) 2024.03.09
requestAnimationFrame  (0) 2024.03.07
push와 splice  (1) 2022.12.01
type check에 대해  (0) 2022.04.24
호이스팅에 대해  (1) 2022.04.23