자바스크립트(JS) 엔진의 구성과 동작 원리 (1)

Jin Seok Kim·2022년 11월 4일
1

자바스크립트

목록 보기
1/2

💡 자바스크립트의 언어적 특징과 동작 방식에 대해 개인적으로 정리한 내용을 기록합니다. 잘못된 부분에 대한 지적이나 피드백 감사드립니다 :)


1. JS의 언어적 특징

1) 인터프리터 언어

: 소스코드를 기계어로 변환하는 과정(컴파일)없이
런타임에 한 줄씩 바이트코드로 변환해가며 명령어를 실행한다

  • 원래 인터프리터 언어는 코드 실행속도는 컴파일 언어에 비해 비교적 느리다.
    • 컴파일 언어는 빌드과정 자체는 소요시간이 있으나, 런타임 환경에서는 이미 기계어로 변환되어 있어 실행속도가 빠르기 때문
  • 단, 최근 웹 브라우저의 JS 엔진(Ex. 크롬의 V8) 대부분은 JIT 컴파일러를 내장하고 있어 실행속도가 매우 빨라졌다.
  • JIT 컴파일러 : 기본적으로 인터프리터 방식으로 동작하지만, 자주 반복되는 코드들은 최적화를 위해 엔진에서 미리 컴파일해둔다
  • 컴파일러에 대한 어셈블리어를 캐시해두고, 런타임에서 이 캐시값을 바로 불러오는 방식이다. 따라서 자주 반복되는 코드들은 바이트 코드에서 어셈블리어로 변환 없이 바로 캐시값을 호출해올 수 있게 된다.
    • 정리하자면, 자바스크립트는 인터프리터 언어임과 동시에 내부 엔진에서 컴파일 과정을 거치는 언어이다

🤔❓ JITC(JIT 컴파일러) & AJITC(Adaptive JITC)

  • JITC의 장점은
    1. 정적 컴파일과 같이, 미리 바이트 코드로 변환하지 않으므로 런타임에 바이트 코드로 변환하기 때문에 동적 타입 결정이 가능하며
    2. 일부 최적화(자주 사용하는 코드의 어셈블리어를 캐시해서 사용)한다

💡 - 그럼 무조건 JITC가 좋은거네?
👉 Nope
JITC의 최적화는 자주 사용하는 코드에 대한 최적화를 진행하기 때문에, 런타임에 동적으로 변수 타입이 자주 변경되는 경우, 기계어로 변환 캐시해둬야하는 양이 매우 많아져 최적화의 장점이 퇴색된다. 이런 경우엔 그냥 인터프리터 방식으로 실행하는 것이 낫다.

💡 - 그럼 인터프리터 언어가 더 좋은건가?
👉 Nope
위의 문제를 극복하기 위해 AJITC가 등장하게 되었는데 모든 코드를 일괄적으로 최적화하지 않고 반복 수행 정도에 따라 가중치를 두어 유동적으로 서로 다른 정도로 최적화 수준을 적용하는 방식이다.

AJITC의 동작 원리

  • Runtime Profiler
    : 함수 호출 빈도를 측정해, JITC/인터프리터 여부를 결정한다.
  • 단, 이 경우에도 타입이 변하는 경우엔 마찬가지로 비효율이 발생할 수 있다.
  • ⭐️ 따라서, 한 변수에 여러 타입을 동적으로 할당하는 것은, JITC,AJITC의 최적화 프로세스를 망치는 행위라고 볼 수 있음
    - ⭐️ 자바스크립트에서 상수값을 변수화해서 활용하고, 함수의 재활용성을 높이는 것이 중요한 이유

2) 동적 프로토타입 기반의 객체 지향 언어

: JS는 프로토타입 기반으로 객체 지향을 지원하며, 개체가 생성된 이후에도 동적으로 프로퍼티와 메소드를 변경할 수 있다


3) 동적 타입 언어

: 런타임 이전에 변수 타입이 결정되는 정적 타입 언어 (JAVA, C++)과 달리 변수 타입이 존재하지 않으며 런타임에 변수 타입이 변경될 수 있다


4) 함수는 일급 객체

: 함수를 객체 취급하며, 인수로 넘길 수 있어 함수형 프로그래밍을 지원한다


2. JS 엔진의 구성

1) 메모리 힙 (Memory Heap)

: 데이터를 임시 저장하는 곳으로 변수, 함수 값들을 저장하고 메모리를 할당하는 공간

  • 어플리케이션이 운영체제로부터 미리 할당받는 메모리 영역이다. 동적으로 메모리 요구량을 바꾸면 상당한 오버헤드가 발생하기 때문에 힙 메모리를 사용한다.

2) 콜 스택 (Call Stack)

: 코드가 실행되면 작업들의 실행 순서를 기록하고 하나씩 순차적으로 수행하는 공간

  • 실행되어야할 코드들을 순서대로 기록하며, 순차적으로 코드를 실행할 수 있게 도와준다
  • 자바스크립트를 싱글 쓰레드(동기식) 언어라고 표현하는 이유는, JS 엔진에 콜스택이 하나밖에 존재하지 않는다는 뜻

🤔❓ 싱글 쓰레드 언어 & 비동기 콜백 ?

💡 JS 엔진은 싱글 쓰레드 언어로 콜스택이 하나밖에 존재하지 않는다면, 동시에 여러 작업을 수행할 수 없다. (동기식 언어)

  • 그런데 어떻게 브라우저 환경에서 여러 작업을 진행되는 동안 Block이 일어나지 않고 비동기 작업을 처리할 수 있는건가?

  • 자바스크립트 엔진은 싱글 쓰레드이지만, 자바스크립트가 실행되는 런타임 환경(브라우저, nodeJS)이 싱글 쓰레드 환경이 아니기 때문.


3. JS 런타임 환경

💡 자바스크립트가 '단일 스레드' 기반의 언어라는 말은 '자바스크립트 엔진이 단일 호출 스택을 사용한다'는 관점에서만 사실이다.

  • 실제 자바스크립트가 구동되는 환경(브라우저, Node.js등)에서는 여러 개의 스레드가 사용되며, 이러한 구동 환경이 단일 호출 스택을 사용하는 자바 스크립트 엔진과 상호 연동하기 위해 사용하는 장치가 바로 '이벤트 루프' 이다.

1) Web API

: 브라우저에서 제공하는 API. 자바스크립트 엔진에서 정의되지 않았던 setTimeout이나 HTTP 요청(ajax) 메소드, DOM 이벤트 등의 메소드를 지원

2) Task Queue(Callback Queue)

: 이벤트 발생 후, 호출되어야 할, 콜백 함수들이 기다리는 공간. 이벤트 루프가 정한 순서대로 줄을 서 있으므로 콜백큐라고도 부른다.

  • 자바스크립트에서 비동기로 호출되는 모든 함수들은 호출 스택(Call Stack)에 쌓이지 않고 이 태스크 큐(Task Queue)로 보내진다.

3) Event Loop

: 이벤트 발생시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정

  • 자바스크립트는 이벤트 루프를 이용해서 비동기 방식으로 동시성을 지원할 수 있게 된다.
  • 이벤트 루프는 현재 자바스크립트 엔진의 콜스택이 비어있는지 주기적으로 확인하고, 태스크 큐에 작업(콜백 함수)이 존재한다면 콜백 함수를 콜스택으로 옮긴다

🤔❓ 이벤트 루프는 왜 콜스택이 비어있는지 확인할까

💡 이벤트 루프가 반드시 Call Stack이 비어져있는 상태에서만 Call Stack으로 Push 하는 이유는 자바스크립트라는 언어가 동기화 문제를 안는 것을 피하고 단일 스레드 언어라는 것을 보장해주기 위함이다.

단일 스레드 환경에서 함수가 실행되고 있는 도중에, 새로운 작업들을 계속해서 콜 스택에 밀어넣게 된다면, 작업의 진행 순서를 보장할 수 없는 동기화 문제가 발생하게 되기 때문


4) Garbage Collector(GC)

: 콜스택과 메모리 힙은 한정적인 자원이므로, 이 공간을 효율적으로 관리하기 위해 더 이상 효용가치가 없다고 판단되는 변수, 함수 등을 함수 실행 종료 후 메모리 힙에서 제거하는 동작을 수행한다.


동작 원리 : Mark and Sweep 알고리즘

💡 "더 이상 필요없는 오브젝트"를 "닿을 수 없는 오브젝트"로 정의한다.

  • roots 라는 오브젝트의 집합을 가지고 있다(자바스크립트에서는 전역 변수들을 의미). 주기적으로 가비지 콜렉터는 roots로 부터 시작하여 roots가 참조하는 오브젝트들, roots가 참조하는 오브젝트가 참조하는 오브젝트들... 을 닿을 수 있는 오브젝트라고 표시한다. 그리고 닿을 수 있는 오브젝트가 아닌 닿을 수 없는 오브젝트에 대해 가비지 콜렉션을 수행한다.

  • 즉, 전역 환경에서 더 이상 참조할 수 없는 변수나 함수에 대해 가비지 콜렉션을 수행한다


4. JS 엔진 내부 동작 방식 상세

업로드중..

1) 토크나이저 : Lexical analyzer, Scanner

: 자바스크립트 소스 코드를 토큰으로 변환한다. 이 과정을 렉시컬 분석(lexical analyze), 토크나이징(Tokenizing), 렉싱(Lexing) 등으로 부름

  • 공백, 주석을 제거한다
  • 전체 코드를 토큰으로 나눈다.

2) 파서 : Syntax analyzer

: 렉시컬 분석을 통해 만들어진 토큰 목록을 트리 구조로 만들며 (= AST(Abstract Syntax Tree)) 이 과정을 파싱(Parsing)이라고 부른다.

  • 이 과정에서 구조적, 언어적으로 문제가 발생할 경우 에러를 뱉는다
  • ⭐️ 이 때 만들어진 AST를 기반으로 babel,prettier,jst2flowchart에서 트랜스파일링을 진행할 수 있다.

3) 인터프리터

: AST를 바이트 코드로 변환한다.


다음 글에 이어서 👉 자바스크립트(JS) 엔진의 구성과 동작 원리 (2)

profile
블로그 자료 마이그레이션 중입니다

0개의 댓글