[Node.js] Memory management

Falcon·2021년 8월 28일
1

javascript

목록 보기
7/28
post-thumbnail

🎯 Goal

  • Javascript V8 Engine 의 메모리 구조를 알고 기능을 설명할 수 있다.
  • Event Loop 동작 원리를 이해한다.

Memory structure

JavaSript Runtime (ex. Nodejs) has single thread!!

Call stack

함수가 load-unload 되는 공간.

LIFO 구조를 갖는 이름 그대로의 스택형 메모리다.
함수단위로 실행을 종료할 때, 해당 함수는 unload(pop)된다.

Heap

function, object, variable 모든 것을 담는 unstructured momory 공간

garbage collector 가 존재한다.

Callback Queue == Task Queue == MacroTasks == Message Queue

실행할 함수 목록을 갖고있는 메시지 큐

콜스택에 충분한 공간이 있으면 메시지 안에 담긴 함수 리스트를 콜스택에 로드하여 실행시킨다.
이 때, 이벤트에 대한 '콜백'함수가 제공되면 외부 비동기 이벤트의 '응답' 메시지 (응답되는 콜백함수 자체)가 큐에 또 쌓인다.

Callback queue example

  • Expired time callback
    ex) setTimeout()
  • I/O Polling callback
    ex) readFile()
  • setImmediate()
  • close event callback
    Event.on('close',()=>{});

ex) setTimeout, fetch, I/O tasks

Job Queue == Micro Tasks

Promise 객체나 async/await 함수의 콜백함수가 큐에 들어간다. Callback Queue가 아닌 Job Queue에 들어간다.

Callback Queue 보다 높은 우선순위를 가진다.

ex) Promise, process.nextTick

💡 Event loop 은 Job Queue 가 비워져야 Callback Queue 를 검사한다.
(Job Queue > Callback Queue)

process.nexttick queue


Core Engine element

Thread-Pool

백그라운드에서 시간이 흐를 때, event loop 은 non-block 상태로 계속 돌고, I/O 작업시 내부적으로 Thread-Pool 의 thread 를 사용한다.

이 때 Thread-Pool 은 libuv 라는 커널 비동기I/O 를 지원하는 라이브러리에 존재한다.
📝 기본적으로 libuv 는 4개의 쓰레드가 존재한다.

API's (Web or C++)

built in funciton 으로 setTimeout, fetch 같은 메소드가 있다.
setTimeout은 호출되자마자 콜스택에서 팝되고 백그라운드에서 시간이 흐른다. 지정된 시간이 지나면 Queue 로 등록된 콜백함수를 보낸다.

Event Loop

콜스택과 큐를 모니터링한다.
콜스택이 비어있을 때 큐의 첫번째 메시지를 콜스택으로 넣는다.

V8 Engine Architecture

libuv Architecture overview

Example Code

// blocking in callstack
function waitSecond(second: number) {
    const nowSecond = Math.floor(Date.now() / 1000);
    // 시간이 3초 흐를 때 까지
    while (Math.floor(Date.now() / 1000) < nowSecond + second) {
      // @NOTHING TO DO 
    }

    return;
}

function main() {
    // push call stack console.log
    console.log(1);
    // insert message to queue
    // Event loop monitoring call stack and queue, if stack is empty, push first message of queue to call stack
    // to speak correctly, callback function 'console2' is pushed to queue.
    setTimeout(function console2(){
        console.log(2);
    }, 0)

    // push call stack waitSecond (setTimeout is not in call stack, it's in queue until stack is empty)
    waitSecond(3);
    // console '3' and return main function (POP)
    console.log(3);

    // now stack is empty state => Event Loop check stack is empty => push 'setTimeout' into Call stack
    return;
}

출력 결과

1
3
2

실행 순서와 메모리에서 일어나는 일

  1. main 함수를 콜스택에 넣는다.
  2. console.log(1) 콜스택에 넣고 실행시킨 후 pop 한다.
  3. setTimeout 을 콜스택에 넣고console(2) 백그라운드에서 타이머를 가동시킨다. (0ms라 바로 타이머는 끝나자마자 콜백 함수를 queue 에 넣는다.)
  4. setTimeout 을 콜스택에서 pop 한다. // 3에서 타이머가 만료되지 않아도 곧바로 콜스택에서 제외된다.
  5. waitSecond 를 콜스택에 넣고 실행한다.
  6. 3초간 블락된 후 waitSecond 를 pop 한다.
  7. console.log(3) 을 콜스택에 넣고 실행시킨 후 pop한다.
  8. event loop이 콜스택이 비어있다는 것을 감지하고 setTimeout 에서 queue에 넣어둔 console2 메소드를 꺼내서 콜스택에 넣는다.
  9. console2 메소드를 콜스택에 넣어 실행시키고 리턴한다.
  10. 프로그램 종료.

setTimeout에서 타이머를 0ms 로설정해도 '2'가 마지막에 찍히는 원인을 알게되었다.
사실상 비동기함수는 모두 '콜백'함수를 Queue 에 넣는 것과 다를바 없다.


🔗 Reference

profile
I'm still hungry

0개의 댓글