본문 바로가기
Javascript/Javascript

[Javascript] 콜스택(Call Stack), 메모리힙(Memory Heap), 가비지컬렉션, 메모리릭 그림으로 이해하기

by img 2022. 3. 16.

Call Stack과 Heap

시작하기에 앞서 하나의 스레드 당 하나의 스택메모리를 사용하는데, 자바스크립트는 싱글스레드 언어이기 때문에 콜스택이라는 스택메모리 하나만 사용합니다. 

const a = 'Hello World!';
const b = [1, 2, 3];
const c = { id:'yoy', password:1234 };
const d = function(){ console.log() };

 

이런 코드가 있다고 했을때, 자바스크립트 엔진에서 Call Stack 영역과 Heap 영역에서 어떻게 동작하는지 알아보도록 하겠습니다.

우선 각 변수 a, b, c, d는 call stack의 실행컨텍스트 렉시컬환경이라는 곳에 { name : value } 형태로 저장됩니다.
비록 초기화 단계에서는 { 변수이름 : undefined } 또는 { 함수이름 : function() object } 로만 저장되지만, 그래도 해당 변수의 이름을 식별할 수 있게됩니다. 우리는 이것을 '식별자 해결' 이라고 부릅니다

그럼 이제 각각의 변수가 어떻게 값을 가지고 있는지 살펴보겠습니다.

원시타입 ( a )

콜스택 메모리에는 단순 변수와 같은 원시 타입 데이터가 저장되는데, 자바스크립트에서 원시타입이란 string, number, boolean, null, undefined 을 말합니다. 

const a = 'Hello World';

  변수 a는 콜스택 영역에 있는 a의 주소를 읽고, 그 콜스택 영역에 그대로 'Hello World'라는 값 자체가 저장되어 있습니다. 

참조타입 ( b, c, d)

반면 복잡한 객체와 같은 참조타입(Reference type) 데이터는 힙 영역에 저장되고, 자바스크립트에서 참조타입으로는 Array, Object, Function 등이 있습니다. 

const b = [1, 2, 3];
const c = { id: 'yoy', pw: 1234 };
const d = function(){ console.log() };

변수 b, c, d가 참조타입에 해당하는데요. 참조타입 변수들도 원시타입과 마찬가지로 콜스택에 각각의 주소를 읽고 있는데, 그 주소에 적혀있는 것은, 배열이나 객체와 같은 데이터가 아니라 힙 영역의 주소들이 들어있습니다.
저는 이해를 돕기 위해 Heap영역을 '빌라'라고 표현했는데요. 예를들어 변수 let b = [ 1, 2, 3 ] 이 읽고 있는 콜스택의 주소를 살펴보니 "Heap빌라의 201호로 찾아가라" 라는 데이터가 있고 Heap빌라의 201호로 찾아가보니 [ 1, 2, 3 ] 이라는 데이터가 살고 있었습니다. 그래서 console.log(b)를 했을 때, [1, 2, 3]이라는 배열 데이터를 얻을 수 있게 되는 것입니다. 

const / let

let과 const의 차이점은 데이터 값이 변경될 가능성이 있을 때는 let을, 변경되지 않을 때는 const를 사용한다고 대부분 알고 계실텐데요. (https://imagineu.tistory.com/5 참고) 하지만  

 

[ES6+] var, let, const 쉽게 이해하기! 3줄요약 + 설명

let 변수와 const는 ES6 이후 스펙에서 새롭게 등장한 변수이다. 그래서 브라우저 배포용 코드같은 경우는 아직도 var 변수만 사용되는 경우도 있다고 한다. var, let, const 를 구분하는 가장 중요한 점

imagineu.tistory.com

const c = { id: 'yoy', pw: 1234 };
c.gender = 'F';
console.log(c);	// {id: 'yoy', pw: 1234, gender: 'F'}

 

위의 코드는 const로 선언되 c의 데이터가 변경되었는데도 에러가 발생하지 않고 정상적으로 작동합니다. 왜일까요? 
변경 유무는 데이터의 값이 아니라, 콜스택의 값을 기준으로 판단하기 때문입니다.

그렇기 때문에 가르키고 있는 원시타입의 데이터나 힙영역의 주소는 변경할 수 없지만, 실제 참조되고 있는 값은 바뀔 수 있습니다. 

가비지 컬렉션

그럼 만약,  변수 a가 let으로 선언되었다고 가정해보겠습니다.

let a = 'Hello World';
a = 'This is yoy';

그럼 더이상 콜스택에 남아있던 원시타입 데이터 'Hello World'는 사용되지 않으므로, 가비지 컬렉터가 쓸모없어진 메모리를 해제합니다. 이와 같은 맥락으로 로컬 스코프를 떠난 후 해당 스코프의 변수가 외부 스코프에서 참조되지 않을 때도 나중에 메모리에서 해제되게 됩니다. 

자바스크립트는 가비지컬렉터가 자동으로 메모리를 관리해주어 메모리 관리를 완벽하게 할 필요는 없다는 장점이 있지만(메모리를 신경 쓸 필요가 없다는 이야기가 아닙니다..), 쓸모없어짐과 동시에 해제되지 않고 가비지 컬렉터가 동작할 때에 해제되기 때문에 최적의 메모리 관리를 할 수 없다는 단점이 있습니다. 

메모리 릭 (메모리 누수)

메모리 힙은 콜스택 영역보다 훨씬 더 큰 공간을 가지고 있지만, 그것이 힙영역이 무한하다는 이야기가 아닙니다. 메모리 힙이 부주의나 일부 프로그램의 오류로 인해 제대로 관리되지 않았을 경우 자동 또는 수동으로 메모리가 해제되지 않아 데이터들이 메모리 공간의 범위를 넘어설 수 있는데, 그것을 메모리 누수라고 합니다. 과거에 사용되었지만, 메모리 힙에서 제거되지 않고 계속 차지하고 있는 현상을 의미합니다. 

메모리 누수가 나타나는 패턴으로는, 

1. 전역변수를 너무 많이 만들경우
2. 사용이 완료된 이벤트리스너를 제거하지 않고 계속 addEventListener만 될 경우
3. setInterval() 함수를 사용했다가 해제하지 않은 경우
4. 사용하지 않지만 콘솔로 출력하는 경우
5. DOM에서 removeChild()되었지만 여전히 해당 node를 querySelector등으로 사용하고 있는 경우 

가 있습니다. 

 

댓글