[Javascript] Javascript - 메모리 관리
Javascript - 메모리 관리
Javascript에서 메모리 관리
- 저수준 언어의 메모리 관리: 메모리 할당/해제 메서드 사용.
- 자바스크립트의 메모리 관리: 가비지 컬렉션을 통해 자동으로 메모리 할당 및 해제.
메모리 생존 주기
- 필요할 때 할당.
c:malloc()javascript: 언어적으로 명시 X
- 할당된 메모리 사용.
- 읽기 및 쓰기:
- 할당된 메모리에 값을 넣을 수도
- 거기에 저장되어 있는 값을 읽을 수도
- 읽기 및 쓰기:
- 필요하지 않으면 해제.
c:free()javascript: 언어적으로 명시 X
메모리 할당
변수 및 함수 선언 시
// primitive type 메모리 할당
const test = “test-text”
// object type 메모리 할당
const obj = {
one: 1,
two: 2
}
const arr = ["one", "two", "three"]
// 함수 메모리 할당
function squareFunc(num) {
return num*num
} // 함수를 저장하기 위해 메모리 할당
문자열, 숫자 등 primitive type 부터 object type 까지 선언 시 메모리 할당.
함수 또한 객체이기 때문에 함수 선언 시 메모리 할당.
함수 호출 시
const today = new Date(); // Date 생성자 함수를 호출하면 메모리 할당.
const myDiv = document.createElement("div"); // div 엘리먼트를 위한 메모리 할당.
Date 클래스(생성자 함수)를 사용하면 date 객체가 생겨 메모리 할당됨.
엘리먼트도 마찬가지.
스택: 정적 메모리 할당
- 컴파일 타임에 크기를 알고 있는 데이터
- primitive type, 참조(객체와 함수를 가리키는)
- 고정된 메모리 크기 할당.
힙: 동적 메모리 할당
- 런타임에 크기를 알게 되는 데이터
- 필요에 따라 더 많은 공간을 할당.
- 객체(함수 포함)
할당
const user = {
id: 1,
name: "blah",
}; // user 객체 자체는 동적 메모리 -> 힙에 할당
재할당
let variable = "init"; // 스택에 할당
variable = "initial"; // 스택에 메모리 새로 할당
JS에서는 기본적으로 primitive type들은 immutable
→ 할당된 메모리에 들어있는 값을 변경하는 것이 아님.
새로 메모리를 할당 받아 가리키게 되는 메모리 위치를 변경해주는 것.
메모리 사용
변수나 객체 속성의 값을 읽고 쓰거나 함수 호출 시 함수에 인수를 전달할 때 메모리에 저장되어 있는 것을 사용하여 수행.
메모리 해제 ** (중요)
→ Javascript 에서는 자동 메모리 관리 방법 가비지 컬렉션(GC) 사용
메모리 할당 추적, 메모리 블록 필요 없는지 판단하여 회수
가비지 콜렉션
Reference-counting 가비지 콜렉션
아무 것도 참조하지 않는 오브젝트를 가비지로 처리.
let company = "startup";
let team = {
cs: "whoever",
marketing: "none",
product: ["dev", "design", "po", "qa"],
};
function sprint() {
return "tired";
}
let ourTeam = team;
let productTeam = team.product;

company는 원시 타입을 가지고 있기에 스택에 쌓임.- 스택에 객체를 가리키는
team이라는 참조가 쌓임. - 객체 자체는 힙에 있음.
- 스택에
ourTeam참조가 쌓임 ourTeam도team이 가리키는 똑같은 메모리 위치를 가리킴.productTeam이라는 참조가 스택에 쌓임.- 힙에 있는 메모리(
team.product의 값이 있는)가 있고, 그걸productTeam이 가리킴.
team = null;
ourTeam = null;

team과ourTeam이null이 되어 가리키고 있는 게 끊어지며 이 쓸모없는(참조되지 않는) 메모리 영역은 가비지 컬렉션의 대상이 됨.- 그러나
productTeam이 가리키는 메모리 영역은 그대로 남아있음.team객체 메모리가 사라졌다고productTeam이 사라진 건 아님.
그러나, 이 알고리즘은 순환 참조를 고려하지 못함. 😭
let you = {
name: "your name",
};
let me = {
name: "my name",
};
you.me = me;
me.you = you;

you = null;
me = null;

you와 me를 서로 참조하게 하고(순환 참조) you 참조와 me 참조를 끊어버려도 힙에서는 메모리 영역이 계속 남아있게 됨.
Mark-and-sweep 알고리즘
닿을 수 없는 오브젝트를 가비지로 처리
roots(전역 오브젝트/브라우저에서는 window, NodeJS 환경에서는 global) 객체에서 시작해서 참조하여 도달할 수 없는 객체를 찾음.
도달할 수 없으면 가비지로 mark하고, 이후에 sweep하게 됨.
let team = {
cs: "whoever",
marketing: "none",
product: ["dev", "design", "po", "qa"],
};
let anotherRefTeam = team;
let productTeam = team.product;
team = null;
anotherRefTeam = null;
console.log(productTeam);
let you = {
name: "your name",
};
let me = {
name: "my name",
};
you.me = me;
me.you = you;
you = null;
me = null;
const unusedValue = "will not be used";

- 순환 참조 되고 있던 것들은 스택의 참조들이
null로 끊어졌기 때문에window에서 접근할 수 있는 방법이 없음. unusedValue같은 경우는 아무데도 사용되지 않으므로 가비지 컬렉션의 대상이 된다.productTeam는console의log메서드의 인자로 참조를 사용하고 있기 때문에 reachable하여 가비지 컬렉션의 대상이 되지 않는다.productTeam도 아무데도 사용되지 않으면 가비지 컬렉션의 대상이 된다.
메모리 낭비
가비지 콜렉터의 Trade-offs
- 아무리 알아서 해준다고 해도 청소되는 것은 필요 없을 때 되는 게 아니라 주기적으로 되는 것이기 때문에 생각하는 것 보다 메모리를 많이 잡아먹을 수 있다.
- 콜렉션이 주기적으로 실행되는 것을 개발자가 정확히 언제 일어나는지 알 수가 없다.
- 컬렉팅하게 되는 가비지의 양이 많거나 빈도수가 많아져도 성능을 잡아먹게 됨. → 그래도 다행인건 유저나 개발자가 신경 쓸 정도는 아니라고 함.
전역 변수
var 혹은 아예 키워드를 쓰지 않으면 전역 변수에 저장 됨.
(window 객체의 속성으로 할당 됨.)
→ function 키워드도 마찬가지라고 함 🙀
사용하지 말라기 보다 null로 참조를 해제해주면 좋음.
잊어버린 타이머
// 잊어버린 타이머
const object = {};
const intervalId = setInterval(function () {
// 여기에서 사용된 모든 것들은 interval이 클리어될 때까지 수집되지 않음.
doSomething(object);
}, 2000);
2초마다 실행되므로 interval이 끝나지 않는 이상 저 스코프 안에서 참조된 객체들은 수집되지 않음.
clearInterval(intervalId);
clearInterval로 타이머 취소.
→ SPA에서 특히 조심. 페이지가 변경되어도 백그라운드에서 실행되므로
구독 취소를 잊어버린 이벤트 리스너
이벤트 리스너 콜백은 예전에는 수집 못했는데 요새 브라우저는 다 한다고 함.
그래도 remove 해주자!
DOM 참조
const elements = [];
const element = document.getElementById("button");
elements.push(element);
function removeAllElements() {
elements.forEach((item, index) => {
document.body.removeChild(document.getElementById(item.id)) **
elements.splice(index, 1); // elements 배열 mutate하여 요소 제거**
});
}
JS에서 DOM을 참조하여 배열에 저장해두었고, 엘리먼트는 지웠지만
elements 객체는 여전히 살아있으므로 메모리를 잡아먹는다.
따라서 elements에서 참조하고 있는 요소들 모두 제거.
참고 링크
https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_management
https://felixgerschau.com/javascript-memory-management/
(보면 좋을 것 같은 글들)
https://yceffort.kr/2020/07/memory-leaks-in-javascript
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
댓글남기기