프로세스 메모리
프로세스는 실행되고 있는 프로그램으로 프로세스는 각 메모리 공간을 시스템으로부터 할당받는다
Text(code)
- 코드와 상수가 정의
- CPU가 해당 영역에서 명령어를 하나씩 가져와 처리
- 읽기만 가능해 데이터를 저장하려고하면 충돌이 발생해 프로세스가 중지
Data
- 전역 변수와 정적 변수가 저장되어있는 영역
- 초기화된 전역 변수가 존재 (비휘발성 메모리 ROM에 저장)
BSS(Block Stated Symbol)
: 초기화되지 않은 전역 변수가 저장
Heap
- 필요에 따라 동적으로 할당되는 동적 메모리 영역
- 메모리 주소값에 의해서만 참조되는 영역
- 런타임(프로그램 동작 시)에 크기가 결정
Stack
- 함수 인자값, 함수 내 지역 변수, 함수 반환 주소 등(잠시 사용되었다가 사라지는 데이터)이 저장되는 영역
- 함수 호출의 전반적인 처리와 리턴을 담당
- 컴파일 타임(로드 시)에 크기가 결정
스택 프레임
- 함수가 호출되었을 때 함수가 가지는 공간 구조
- 함수가 동작을 종료하고 복귀 주소로 돌아갈 때 스택 프레임은 소멸
SP(Stack Pointer)
: 현재 위치를 가리키는 레지스터
BP(Base Pointer = Frame Pointer)
: 함수의 시작 시점으로 스택 프레임이 소멸하기 전까지 고정값으로 해당 값을 기준으로 삼아 상대적 주소, Offset을 사용할 수 있다
프로세스 메모리 관리 - Node.js
V8 Engine
- JavaScript는 인터프리터 언어(코드를 한줄씩 읽어가며 명령을 바로 처리 - 번역과 실행이 동시에 발생)로 코드를 해석하고 실행하는 실행기가 필요 →
V8 Engine
- JS를 Assembly code로 컴파일
- Node.js는 V8 Engine을 떼어와서 JS를 실행할 수 있는 자바스크립트 런타임
JIT Compiler
JIT Compiler(Just-In-Time)
는 동적 컴파일(인터프리터)과 정적 컴파일(Compiler)을 합친 것으로 실행시점에서 인터프리터 방식으로 바이트 코드를 생성
- 바이트 코드를 기계어 코드로 만들고 캐싱해 같은 함수가 여러번 불릴 때 매번 기계어 코드가 생성되는 것을 방지 (인터프리터의 비효율성 보완)
- V8 Engine은 JavaScript 컨텍스트 당 하나의 프로세스를 사용
→ Resident Set
- 메모리를 크게 Stack, Heap 영역으로 구분
Heap
- New 영역과 Old 영역으로 나뉘어
Garbage Collection
으로 관리
New Space
: 짧은 생명주기를 가져 Minor Garbage Collection
에 의해 관리
Old Space
: 두 번의 Minor Garbage Collection 주기동안 New Space에서 살아남은 객체들로 Major Garbage Collection
에 의해 관리
Large Object Space
: 매우 큰 객체들이 존재해 Garbage Collection에 걸리지 않음
Code space
: JIT 컴파일러가 컴파일된 코드 블록을 저장 - 실행 가능한 메모리가 존재
Stack
- Frame 단위로 묶여서 올라간다
- 함수가 호출되면 해당 함수 이름으로 된 Frame 영역이 생기며 함수가 끝나기 전까지 해당 Frame 영역에 변수들이 적재
Minor Garbage Collection (Scavenger)
- New Space 영역을 정리하는 역할
- 새 객체를 위한 공간을 예약하기 위해 할당 포인터가 증가할때 포인터가 공간의 끝에 도달하면 GC가 트리거
- 자주 발생하며 빠르게 진행
Major Garbage Collection
- Old Space 영역을 정리하는 역할
- Old Space 공간이 부족하다고 판단할 때 트리거
가상 메모리
메모리를 관리하는 방법 중 하나로 각 프로그램에 실제 메모리 주소가 아니라 가상의 메모리 주소를 주는 방식
→ 실제 물리 메모리가 가지고 있는 크기를 논리적으로 확장해서 사용 가능
- 물리 메모리가 가진 공간 이상으로 데이터를 저장할 수는 없지만 주기억장치나 레지스터 등의 공간을 연속적으로 가상 공간에 매핑
- 가상 메모리는
페이지
단위, 물리 메모리는 프레임
단위로 관리
페이징
- Virtual Memory의 Page가 하나의 Frame을 할당받으면 물리 메모리에 위치
- Frame을 할당받지 못한 페이지는 외부 저장장치에 저장
Page Table
- 프로세스의 페이지 정보를 저장하고 있는 테이블
- Page에 매핑되는 Frame을 찾을 때 참조
key
: 페이지 번호
value
: Page와 매핑된 Frame번호
- 페이지 번호(P)를 사용해 페이지 테이블에 접근
- Page Table에서 해당 번호에 해당하는 Frame 시작 주소를 얻음
- 프레임 시작 주소(F)와 변위(d)를 통해 물리 주소에 접근
- 가상 주소를 물리 주소로 변환하는 과정은 MMU라는 하드웨어가 실행
TLB(Translation Look aside Buffer) - 페이지 정보 캐시
에서 가상 주소를 검색 후 엔트리가 존재하면 바로 물리 메모리로 주소 변환 후 데이터를 가져오고, 엔트리가 없다면 페이지 테이블을 참조해서 변환
- 페이지가 실제 물리 메모리에 없을 때 Page Fault Interrupt 발생
- Page Fault가 발생하면 운영체제가 해당 페이지를 물리 메모리에 올림
페이지 테이블 종류
1. Multi-level Page Table
: 페이지 테이블을 여러 단계로 쪼개어 사용
2. Hashed Page Table
: 해시 데이블에 존재하는 엔트리를 참조해서 물리 주소를 찾음
3. Inverted Page Table
: 프로세스 별 페이지 테이블이 아닌 시스템 하나만의 페이지 테이블을 두는 방식
페이지 교체 정책
1. FIFO
- 가장 먼저 메모리에 올라온 페이지를 가장 먼저 내보냄
- 구현이 간단하지만 성능이 좋지 않음
2. OPT(Optimal) 알고리즘
- 앞으로 가장 오랫동안 사용하지 않을 페이지를 교체
- Page Fault 발생이 가장 적지만 실제로 구현 불가한 정책
3. LRU 알고리즘
- 가장 오랫동안 사용하지 않은 페이지를 교체
- 성능이 좋은 편으로 많은 운영체제가 채택
4. LFU 알고리즘
- 가장 참조횟수가 적은 페이지를 교체
- 교체 대상이 여러 개면 가장 오랫동안 사용하지 않은 페이지를 교체
5. MFU 알고리즘
- LFU와 반대로 참조 횟수가 가장 많은 페이지를 교체
6. NUR(Not Used Recently) 알고리즘 - Clock
- 최근에 사용하지 않은 페이지를 교체하지만 교체되는 페이지의 참조 시점이 가장 오래되었다는 것은 보장하지 못함
- 동일 그룹 내에서 무작위의 선택
Reference Bit
: 페이지가 참조되지 않았을 때 0, 참조되었을 때 1
Modified Bit
: 페이지의 내용이 변경되지 않았을 때 0, 변경되었을 때 1
Fragmentation
- 메모리 공간이 작은 조각으로 나뉘어 사용 가능한 메모리가 충분하게 존재하지만 할당이 불가능한 상태
External Fragmentation
: 메모리가 할당되고 해제되며 작은 메모리가 중간중간 존재 → 사용하지 않는 메모리가 많이 존재해 총 메모리 공간은 충분하지만 실제로 할당할 수 없는 상황
Internal Fragmentation
: 메모리를 할당할 때 프로세스가 필요한 양보다 더 큰 메모리가 할당되어서 프로세스에서 사용하는 메모리 공간이 낭비되는 상황
Fragmentation 해결 방법
Paging 기법
- 가상 메모리를 사용해 External Fragmentation을 해결하는 방법
- 가상 메모리를 같은 크기의 블록으로 나눈 것을 페이지라 하고 RAM을 페이지와 같은 크기로 나눈 것을 프레임이라 할 때, 사용하지 않는 프레임을 페이지에 옮기고, 필요한 메모리를 페이지 단위로 프레임에 옮기는 기법
- 페이지와 프레임을 대응시키기 위해
page table
필요
- 연속적이지 않은 공간도 활용할 수 있어 External Fragmetation 문제 해결 가능
- 필요한 메모리가 page의 배수가 아닌 경우 Internal Fragmentation 발생
- 페이지 크기를 작게하면 Internal Fragmentation 문제를 해결할 수 있지만 paging mapping 과정이 많아져 비효율적일 수 있음
Segmentation 기법
- 가상 메모리를 사용해 Internal Fragmentation을 해결하는 방법
- 가상 메모리를 서로 크기가 다른 논리적 단위 세그먼트로 분할해서 메모리를 할당하는 방식
- 세그먼트들의 크기가 달라 미리 분할할 수 없고 메모리에 적재될 때 빈 공간을 찾아 할당
- mapping을 위한
세그먼트 테이블
이 필요
- 필요한 만큼 할당받아 Internal Fragmentation을 일어나지 않지만 중간에 프로세스가 메모리를 해제하면 생기는 External Fragmentation은 존재
Memory Pool
- 필요한 메모리 공간을 필요한 크기, 개수만큼 사용자가 직접 지정해서 미리 할당받아놓고 필요할 때마다 사용하고 반납하는 기법
- 할당과 해제가 반복되며 Fragmentation을 발생시킬 수 있으나 미리 공간을 할당하고 쓴 후 반납하기 때문에 External Fragmentation가 발생하지 않고 필요한 크기만큼 할당받아 Internal Fragmentation도 발생하지 않음
- 메모리의 할당, 해제가 잦은 경우 효과적
- 할당하고 사용하지 않는 순간에도 할당되어 있으므로 메모리 누수가 존재
참고자료
https://medium.com/@jungkim/스위프트-타입별-메모리-분석-실험-4d89e1436fee
https://deepu.tech/memory-management-in-v8/
https://hwan-shell.tistory.com/343
https://wogh8732.tistory.com/395
https://gamedevlog.tistory.com/85
https://jeong-pro.tistory.com/91