메모리 관리 개념과 Node.JS의 메모리 관리

윤뿔소·2024년 7월 19일

기술 | JavaScript

목록 보기
2/4
post-thumbnail

프로세스 메모리 구조 모델

메모리 관리는 모든 프로그래밍 언어와 시스템에서 중요한 역할을 합니다. 다양한 언어에서 다양한 메모리 관리 방법들이 나왔습니다.

일반적인 메모리 관리

보통 4개의 구역으로 나뉘게 됩니다.

  1. 스택(Stack)
    • 함수 호출 시 지역 변수와 함수 호출 정보를 저장
    • LIFO(Last In, First Out) 구조로 작동
    • 메모리 할당과 해제가 자동으로 이루어짐
  2. 힙(Heap)
    • 동적으로 할당된 메모리를 저장
    • 메모리 할당과 해제가 개발자의 코드에 의해 관리
    • 다양한 크기의 메모리를 할당할 수 있음
  3. 데이터(Data) 영역
    • 전역 변수와 정적 변수를 저장
    • 프로그램 시작 시 할당되고 종료 시 해제
  4. 코드(Code) 영역
    • 실행할 코드와 상수 데이터를 저장
    • 프로그램 시작 시 할당되고 종료 시 해제

일반적인 메모리 관리 방식에서는 개발자가 힙 메모리를 할당하고 해제하는 것을 명시적으로 관리합니다. 이에 대한 애로사항이 많아 어떤 언어에서는 자동적으로 관리해주는 기능을 추가했습니다.

그 중 하나가 Node.JS입니다.

Node.JS의 메모리 관리

Node.js는 자바스크립트 런타임 환경으로, V8 엔진을 기반으로 작동합니다. 이 V8 엔진이 메모리 관리를 도와줍니다.

Node.js의 메모리 관리 방식은 주로 V8 엔진의 가비지 컬렉션(Garbage Collection) 메커니즘에 의해 관리됩니다.

  1. V8 엔진
    • V8 엔진은 구글의 오픈 소스 자바스크립트 엔진으로, Node.js의 핵심 부분.
    • 메모리 할당과 해제를 자동으로 관리하며, 주기적으로 가비지 컬렉션을 수행하여 사용되지 않는 메모리를 해제
  2. 스택(Stack)과 힙(Heap)
    • Node.js에서 스택과 힙의 개념은 여전히 존재
    • 스택은 함수 호출 시 생성되는 지역 변수와 호출 스택을 관리
    • 힙은 객체와 함수가 동적으로 할당되는 메모리 영역
  3. 가비지 컬렉션(Garbage Collection)
    • V8 엔진은 주기적으로 가비지 컬렉션을 수행하여 더 이상 참조되지 않는 객체를 힙에서 제거
    • 주요 가비지 컬렉션 알고리즘에는 Mark and Sweep이 있음
    • 이 방식은 개발자가 메모리 해제에 대해 신경 쓸 필요 없이 자동으로 관리
  4. 메모리 제한
    • Node.js는 기본적으로 힙 메모리에 제한을 두고 있음
    • 64비트 시스템에서는 약 1.4GB, 32비트 시스템에서는 약 0.7GB의 힙 메모리 제한이 있음
    • 이 제한을 초과하면 메모리 부족 오류가 발생할 수 있음
  5. 메모리 사용 모니터링
    • Node.js는 process.memoryUsage() 함수를 통해 메모리 사용량을 모니터링할 수 있음
    • 개발자는 이를 통해 현재 프로그램이 사용하는 메모리 상태를 확인하고 최적화할 수 있음

차이점 요약

요소일반적인 메모리 관리Node.js 메모리 관리
메모리 관리 방식수동(개발자가 명시적으로 관리)자동(V8 엔진의 가비지 컬렉션)
스택과 힙존재존재
가비지 컬렉션없음(수동으로 메모리 해제)있음(V8 엔진의 가비지 컬렉션)
메모리 할당 및 해제개발자가 직접 관리자동으로 관리
메모리 제한보통 제한 없음기본 힙 메모리 제한 존재
메모리 사용 모니터링개발자가 직접 구현process.memoryUsage()로 가능

참고

가비지 콜렉션 임시 구현

이런 느낌이라고 보면 됩니다. STACKHEAP의 주소를 담은 배열입니다.
왜 인지 힙 압축하는 곳이 너무 깔끔해서 좋습니다.

/**
 * 힙 영역에 할당된 타입들 중에서 스택에 포인터 변수가 없는 경우를 찾아서 해제하는 메소드
 * @returns {Array} 힙 상태 배열
 */
garbageCollect() {
  console.log('가비지 컬렉션 시작');

  // 스택에서 참조하는 힙 주소들을 저장할 Set
  const referencedAddresses = new Set();

  // 스택을 순회하며 참조하는 힙 주소들을 수집
  for (let stackAddr of this.STACK) {
    if (this.STACK.length >= 0 && this.STACK.length < this.memorySize) {
      // 힙 영역 주소 범위 체크
      referencedAddresses.add(stackAddr);
    }
  }

  // 힙을 순회하며 참조되지 않는 메모리 해제
  for (let heapAddr = 0; heapAddr < this.HP; heapAddr++) {
    if (this.HEAP[heapAddr] && !referencedAddresses.has(heapAddr)) {
      const type = this.HEAP[heapAddr][0];
      const size = Math.max(8, this.types[type]);

      // 해당 메모리 블록 해제
      for (let i = 0; i < size; i++) {
        this.HEAP[heapAddr + i] = null;
      }

      console.log(
        `주소 ${this.toHexAddress(heapAddr)}${type} 타입 메모리 해제`,
      );
    }
  }

  // 힙 압축 (사용 중인 메모리를 앞으로 모으고 HP 조정)
  let newHP = 0;
  for (let heapAddr = 0; heapAddr < this.HP; heapAddr++) {
    if (this.HEAP[heapAddr]) {
      if (heapAddr !== newHP) {
        this.HEAP[newHP] = this.HEAP[heapAddr];
        this.HEAP[heapAddr] = null;
      }
      newHP++;
    }
  }

  // HP 갱신
  this.HP = newHP;

  console.log('가비지 컬렉션 완료');
  console.log(`새로운 HP: ${this.toHexAddress(this.HP)}`);
}
profile
코뿔소처럼 저돌적으로

0개의 댓글