[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
댓글남기기