컴파일러는 여러 단계를 거쳐 결과물을 만들어낸다.
그런데 이 때 처음에 공통적으로 하는 작업이 있는데 바로 파싱이다.
이 때 어휘 분석과 구문 분석을 거치는데 어휘분석은 tokenizer, lexer로 나뉜다.
https://velog.io/@song961003/TIL-regexp-ast 여기서 구현한게 어휘분석기 ,구문분석기다.
이렇게 만들어진 구문(Tree)을 가지고서 c 컴파일러는 아래의 과정을 거친다.
의미론적 분석을하고 -> 어셈블리어로 변환후 ->
기계어로 바꾸고 -> 목적파일을 만들고 ->
링크를 해서 -> 바이너리 파일이 생성된다.
이번 시뮬레이터 에서는 실제 전체 메모리 구조를 구현하지는 않았고 Stack, Heap, Text만 구현했다.
Stack은 함수를 호출 할 때마다 지역변수, 매개변수 리턴값 등이 쌓이고
Heap은 동적으로 할당된 메모리가 들어간다.
Text는 어셈블리 명령어 한개씩이 들어가게 된다.
그리고 Stack은 높은 메모리 주소에 저장되고 Text는 그 반대다.
V8 엔진에서 관리하는 메모리를 조금 더 자세히 살펴보면 힙 메모리는 가비지 컬렉션을 수행하는데 모든 영역에서 수행하는게 아니라 New, Old 영역에 대해서만 수행한다.
여기서 New는 새로 만든 객체, Old는 GC가 2번 발생할동안 살아남은 객체를 저장해준다.
Old는 또 두개 영역으로 나뉘는데 pointer는 다른 객체를 참조하는 애들이 모여있고,
data에는 값이 들어있는 애들이 모여있따.
그 외 영역은 아래와 같다.
그리고 스택은 메서드와 함수 프레임, 원시 값, 객체 포인터를 포함한 정적 데이터가 저장된다.
참고로 함수프레임(FP)는 함수의 시작위치가 들어있는 레지스터다. 그 레지스터가 갖고있던 값을 넣는다.
여기 링크를 살펴보면 js 코드를 읽을 때 stack, heap 메모리에 어떤 일이 일어나는지 볼 수 있다.
출처: https://speakerdeck.com/deepu105/v8-memory-usage-stack-and-heap
우선 실행 컨텍스트 ( Execution Context ) 은 실제로 존재하는건 아니고 ECMA Script의 스펙에서 설명하는 추상적인 개념이다. 그래서 실제로 접근할수는 없다.
예전에 이곳에 공부한 내용을 자세히 써놨으니 생각이 안나면 다시 가서 보자!
간단히 다시 적으면 컨텍스트는 자바스크립트 코드가 실행되는 환경을 의미한다.
실행 컨텍스트는 실행 가능한 코드가 실행될 때마다 생성되며, 변수 객체(variable object), 스코프 체인(scope chain), this 바인딩 등의 정보를 포함한다
실행 컨텍스트 에서는 해당 스택 프레임의 정보들(variable objectscope chain, this 바인딩 등의 정보)가 들어가 있다고 생각한다. 그리고 콜 스택에는 이제 실제로 지역 변수들, 함수들 등이 들어가 있다고 생각한다.
js는 실행 환경을 따로 저장하므로 아마도 이 둘을 따로 관리하는것처럼 보인다.
이 둘은 정말 비슷한 개념이고 같다고 생각해도 될것같은데 그래서 콜 스텍은 실행스텍이라 부르는 사람도 존재하는것같다 -> 참조
위에서 힙 메모리에 할당된 값들은 해제를 해주지 않으면 계속 남아있는다.
그래서 V8에선 마이너 GC, 메이저 GC를 돌리며 이 쓰레기 값들의 할당을 해제해준다.
우선 이곳에 순서가 자세히 나와있다.
위에서 힙의 New 공간에서 돌아가는게 마이너 GC인데 간략한 동작 과정은 아래와 같고 to from 영역이 있다.
from 영역에 메모리에 값을 할당하려 한다. 만약, 꽉차있면 마이너 GC가 실행된다.
GC루트(스택 포인터)에서 시작해 from 영역의 객체 그래프를 재귀적으로 탐색하며 현재 사용중인 객체들만 to 영역으로 옮기고, 옮겨진 객체를 가리키던 포인터는 갱신된다. 이 과정이 끝나면 to 공간을 압축해 메모리 단편화를 줄인다.(아래이미지)
to로 못옮겨지고 from에 남은 애들은 가비지로 취급돼 가비지 컬렉트 된다.
to와 from 공간에 있는 객체들을 맞바꿔 to로 옮겨진 객체들은 다시 from에 존재하고, to는 비어있게 됨. (아래이미지)
시간이 흘러 새로운 객체를 from에 할당하려 하는데 공간이 없다면 마이너 GC를 실행함.(아래이미지)
2~4가 반복된뒤 살아남은 객체는 2번 살아남았기에 Old 공간으로 옮겨지게됨.(아래이미지)
to와 from 공간에 있는 객체들을 맞바꿔준후 새로운 객체를 from에 할당(아래이미지)
메이저 GC는 Old 영역에서 사용하지 않는 메모리들의 할당을 해제해고, V8에서 Old 영역의 메모리가 모자르다고 판단할 때 발생한다.
그리고 Old 영역은 마이너 GC 주기가 실행된 후 2번 살아남은 객체들로 채워지게 된다. 근데 마이너 GC의 작동방식은 영역 내의 모든 부분을 탐색하기에 메모리 영역이 크면 정말 비효율적으로 작동할것이다.
그래서 New 영역은 Mars-Seep-Compact 알고리즘을 사용하는 메이저 GC를 사용해 힙 메모리에서 참조되지 않고 있는 메모리들의 할당을 해제해준다.
이 알고리즘은 Tri-color(흰색-회색-검은색) 마킹 시스템을 사용하고 세 단계의 프로세스를 거친다. 그 후 변경된 메모리들의 포인터를 갱신해준다.
그런데 위 두개의 GC는 작동할 때 프로그램이 멈춘다. 이를 stop-the-word라고 한다.
이 시간이 길면 길어질수록 페이지가 느려지거나 렌더링 시간이 길어질 것이다.
이를 피하기 위해서 V8에서는 아래의 기술을 사용한다.
원래 메인 쓰레드 하나가 혼자 일을 하던걸 헬퍼 쓰레드 들과 균등하게 나눠 일을한다.
쓰레드 간의 동기화를 처리해야 해서 오버헤드가 생기지만 stop-the-world 시간이 크게 감소한다.
메인 쓰레드가 작은양의 작업을 간헐적으로 처리한다.
가비지 컬렉션을 수행하는 시간이 분산돼, 좋은 UX의 제공이 가능하다.
메인 쓰레드는 가비지 컬렉션을 하지 않고 헬퍼 쓰레드가 이를 수행한다.
기술적으로 구현이 어렵지만, 메인 쓰레드는 더이상 stop-the-world가 없다는 큰 장점이 있다.
개발자는 GC에 직접 접근할 수 없다.
하지만 v8은 크롬등의 프로그램에게 가비지 컬렉션을 유발할 수 있는 메커니즘을 제공한다.
크롬은 프로그램이 쉬는 free, idle time을 알 수 있다.
예를들면, 크롬은 1초에 60프레임을 제공하는데 1프레임 렌더링에 걸리는 시간은 약 16ms(1s/60) 이다.
만약 애니메이션 프레임 렌더링 작업이 16ms보다 빨리 끝나게 된다면,
크롬은 다음 프레임 작업 전까지 가비지 컬렉션을 유발하게 된다.
오늘 정리한 내용은 목요일에 공부 했던 내용 + 조금 더 찾아본 내용을 다시 정리한 내용이다.
지금까지는 그냥 당일에 공부한건 당일에 정리하고, 다음에는 한번씩 다시 보기만 했었는데
이렇게 시간이 여유로울 때 정리를 하면 몰랐던 내용 혹은 헷갈렸던 내용도 추가로 학습해서 좋은것같다.
그래서 앞으로는 당일에 학습한 내용에 대한 정리를 마치더라도
일요일이 끝나기 전에 해당 내용에서 헷갈리는 개념등을 다시 학습해보면 좋을것같다.
https://ui.toast.com/weekly-pick/ko_20200228
https://jaehyeon48.github.io/javascript/memory-management-in-v8/
https://speakerdeck.com/deepu105/v8-minor-gc?slide=1
https://speakerdeck.com/deepu105/v8-memory-usage-stack-and-heap?slide=1
https://ui.dev/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript
https://fehead.tistory.com/
https://v8.dev/blog/trash-talk
https://fe-developers.kakaoent.com/2022/220519-garbage-collection/