Skip to main content

DeepDive ErrorBoundary vs try-catch

V8, EventLoop, CallStack

동기적 처리 vs 비동기적 처리

1.JS코드의 컴파일은 V8 엔진이 처리한다.

  • 동기적으로 실행되든 비동기적으로 처리되든 간에 V8 엔진은 먼저 해당 코드를 컴파일합니다.
  • 컴파일 단계에서 콜스택을 사용하지는 않는다.
  • 그 이후 (비)동기적처리 한다.
    2.동기적으로 처리 : Call Stack 넣어 실행한다.
    3.비동기적 처리 : TaskQueue에 넣고 이벤트루프에 의해 CallStack으로 이동한다.

V8 엔진

1.V8 엔진은 Google이 개발한 오픈 소스 자바스크립트 엔진

  • 이 엔진은 자바스크립트 코드를 해석하고 실행하는 역할
  • Google Chrome과 Node.js에서 사용
  • 1.1 해석 : 컴파일은 Just-In-Time (JIT) 방식 이용
  • 1.2 실행 : 힙메모리 관리, 가비지 콜렉션, 콜스택

2.Just-In-Time (JIT) 컴파일

배경

  • 인터프리터 : 코드를 한줄한줄 실행하여 빠르지만, 반복적인 코드 실행시 성능 불익.
  • 컴파일러 : 모든 소스코드를 분석 후 바이트 코드를 만들고나서 실행. 빌드타임에 시간이 걸리지만 실행은 빠르다.
  • 바이트 코드 : 파일한 후 생성되는 중간 단계의 코드, 주로 가상 머신(VM)에서 실행되도록 설계된 코드
  • 머신코드 : 컴퓨터의 CPU가 직접 이해하고 실행할 수 있는 최종 형태의 코드

V8엔진은 위 2가지 방식의 이점을 모두 가진다.

  • JIT 컴파일은 실시간으로 코드의 덩어리를 컴파일 한다.
  • 처음에는 바이트 코드로 변환하고, 이를 실행하면서 자주 사용되는(핫 코드) 부분을 감지.
  • 핫 코드는 네이티브 머신 코드(기계어)로 컴파일되어 빠르게 동작하여 성능 이점.

3.Garbage Collection

V8은 메모리 관리를 자동으로 수행하며, 사용되지 않는 메모리를 주기적으로 정리

4.바이트 코드, 네이티브 코드, 핫 코드 감지 (Hot Code Detection)

4.1 바이트코드 생성

  • 처음에 자바스크립트 코드는 바이트코드로 변환됩니다. 이 바이트코드는 인터프리터에 의해 실행됩니다.

4.2 핫 코드 감지 (Hot Code Detection):

  • 코드가 실행되면서 V8 엔진은 자주 실행되는 코드(예: 루프 내에서 반복되는 코드)나 함수(핫 코드)를 감지

4.3 네이티브 코드로 컴파일:

  • V8은 이러한 핫 코드를 최적화 대상이라고 판단하고, 더 빠르게 실행될 수 있도록 네이티브 머신 코드로 컴파일합니다.

4.4 최적화 및 디옵티마이즈(Deoptimization):

  • V8은 실행 중에 최적화를 계속 적용
  • 특정 코드가 예상과 다르게 동작할 경우 최적화를 되돌리는 과정도 수행합니다.

*웹어셈블리어는 많은양의 코드가 기계어(네이티브)어로 변경되어 빠른 성능을 보여준다.

참고) JIT과 Execution Context의 2단계  

1.Creation Phase (생성 단계):
- 변수 객체(Variable Object) 생성, 스코프 체인(Scope Chain), this 바인딩

2.Execution Phase (실행 단계):
- 코드 실행: 자바스크립트 코드를 한 줄씩 실행하면서, 변수와 함수의 값이 할당되고 계산이 수행됩니다.

*JIT 컴파일은 Execution Phase와 동시에 진행되거나 그 직전에 완료되어, 최적화된 코드가 실행 컨텍스트의 2단계에서 사용
- 코드 실행 중 최적화: 이 단계에서 V8 엔진은 코드를 실행하면서 자주 호출되거나 반복되는 "핫 코드" 부분을 감지합니다.

이벤트 루프(Event Loop)

이벤트 루프는 자바스크립트 런타임에서 비동기 작업을 처리하는 핵심 메커니즘입니다.

  • 자바스크립트는 싱글 스레드 언어 -> 콜스택 작업이 끝나야 다음작업이 수행 가능하다.

  • 비동기 작업을 처리할 수 있는 이유가 바로 이벤트 루프 덕분

  • Call Stack: 자바스크립트 엔진은 함수 호출을 관리하기 위해 콜 스택(Call Stack)을 사용합니다.

  • Task Queue:

    • 비동기 작업(예: 타이머, 네트워크 요청 등)은 콜 스택에서 바로 실행되지 않고, 대신 Task Queue에 넣어집니다.
    • 콜 스택이 비워지면 이벤트 루프가 Task Queue에서 대기 중인 작업을 가져와 실행합니다.
  • Event Loop: 이벤트 루프는 콜 스택과 Task Queue를 모니터링하면서

    • 콜 스택이 비워질 때마다 Task Queue에서 작업을 가져와 실행.
    • 이 과정이 반복되면서 비동기 작업들이 순차적으로 처리.

Call Stack

콜 스택은 자바스크립트 엔진의 핵심 구성 요소로, 함수 호출을 관리하는 스택 구조입니다.

  • Push/Pop: 함수가 호출되면 콜 스택에 푸시(Push)되고, 함수가 실행을 마치면 팝(Pop)됩니다.
  • LIFO(Last In, First Out): 콜 스택은 후입선출(LIFO) 방식으로 작동합니다. 가장 최근에 푸시된 함수가 먼저 팝됩니다.
  • 스택 오버플로우(Stack Overflow): 콜 스택이 너무 깊어져 한계에 도달하면 스택 오버플로우가 발생할 수 있습니다. 예를 들어, 재귀 호출이 무한히 일어나면 이런 문제가 발생합니다.

싱글스레드 vs 멀티스레드 vs 멀티프로세스

3가지 방식에 따라 CallStack, Memory 관리에 차이가 있다.

1.싱글 스레드

  • 싱글 스레드 환경에서는 하나의 콜 스택만 존재.
  • 하나의 작업이 완료될 때까지 다른 작업이 실행되지 않는다.
  • 비동기 처리에 이벤트 루프를 활용하지만, 여전히 하나의 콜 스택에서 모든 함수 호출이 처리.

2.멀티 스레드:

  • 멀티 스레드 환경에서는 각 스레드마다 별도의 콜 스택이 존재합니다. (독립적 실행, 각 스레드가 자신의 콜 스택을 관리)
  • 각 스레드가 고유의 스택 메모리를 가지고, 이를 통해 함수 호출과 로컬 변수들을 관리합니다.
  • 각 스레드는 공유의 힙 메모리를 가진다. (동기화 문제, race condition)
  • 가비지 컬렉션은 이 공유된 메모리에 대해 이루어진다. (스레드 간 동기화가 필요,복잡도가 증가)

3.멀티 프로세스

  • 독립된 메모리 공간(힙과 스택)을 사용. 기본적으로 프로세스 간의 메모리 공유는 불가능 (프로세스는 서로 격리)
  • 프로세스 간 통신(IPC, Inter-Process Communication) 메커니즘을 사용.
  • IPC 방법에는 파이프, 소켓, 메시지 큐, 공유 메모리(shared memory)

React 애플리케이션이 실행될 때 V8 엔진과 이벤트 루프가 처리하는 작업

3가지 방식에 따라 CallStack, Memory 관리에 차이가 있다.

V8 엔진이 처리하는 것

  1. JSX 및 JavaScript 코드의 파싱 및 컴파일:
  2. React 컴포넌트 렌더링:
    • V8 엔진은 React 컴포넌트의 렌더링 로직을 실행합니다. 예를 들어, 컴포넌트의 상태(State)와 속성(Props)을 기반으로 UI가 어떻게 표시될지를 결정합니다.
  3. React의 라이프사이클 메서드 실행:
    • componentDidMount, componentDidUpdate, componentWillUnmount 같은 React의 라이프사이클 메서드는 JavaScript 함수입니다.
  4. DOM 조작:
    • React는 가상 DOM(Virtual DOM)을 사용하여 실제 DOM을 효율적으로 업데이트합니다. V8 엔진은 이 가상 DOM의 비교 작업과 실제 DOM 업데이트 명령을 처리합니다.

이벤트 루프가 처리하는 것

이벤트 루프는 React 애플리케이션에서 비동기 작업을 처리하는 데 관여합니다. 주요 역할은 다음과 같습니다:

  1. 비동기 작업 처리:
    • React 애플리케이션에서 타이머 함수(setTimeout, setInterval)나 비동기 API 요청(fetch, axios)을 사용할 때,
  2. React의 비동기 렌더링:
    • React 18부터 지원하는 Concurrent Mode에서 React는 비동기 렌더링을 할 수 있습니다.
  3. 마이크로태스크 및 태스크 큐 관리:
    • Promise 객체의 비동기 처리도 이벤트 루프의 마이크로태스크 큐에 의해 관리됩니다.
  4. 사용자 이벤트 처리:
    • 사용자로부터 발생하는 이벤트(예: 클릭, 키보드 입력 등)는 이벤트 루프를 통해 처리됩니다. 이벤트가 발생하면 이벤트 루프는 관련된 이벤트 핸들러를 콜 스택으로 전달하여 실행합니다.

ErrorBoundary의 처리하는 애러

1.ErrorBoundary (React의 에러 경계)

  • 1.1 적용 범위: 랜더링 프로세스의 애러를 잡는다.
    • ErrorBoundary는 자식 컴포넌트 트리에서 발생한 에러만 포착.
    • (Concurrent Mode 가 아닌 경우) 메인스레드는 랜더링에 전념하며 다른 작업들을 블락킹 한다.
  • 1.2 컴포넌트 자체를 try-catch로 감싸면
    • 1.componentDidCatch와 같은 라이프사이클 메서드를 통한 오류 작업 불가능
    • 2.하위 노드의 오류 캐치 불가능
  • 1.3 실행 컨텍스트가 다름 : 이벤트 핸들러, 비동기 작업(예: setTimeout, fetch), 서버 사이드 렌더링에서 발생하는 에러는 처리하지 못한다.

2.이벤트 루프 (JavaScript의 비동기 처리 메커니즘)

  • 적용 범위: 이벤트 루프는 비동기 작업이나 이벤트 핸들러에서 발생한 에러를 처리

*2가지 방식의 오류 처리는 실행컨텍스트가 달라서 그렇다.