자바스크립트는 눈에 보이지 않는 곳에서 메모리 관리를 수행한다.
원시값, 객체, 함수 등 우리가 만드는 모든 것은 메모리를 차지한다.
🤔 그럼 이제 안 쓰는 것들은?
지금부터 자바스크립트 엔진이 어떻게 필요 없는 것을 찾아내 삭제하는지 알아보자.
💡 도달 가능성(reachability)란?
어떻게든 접근하거나 사용할 수 있는지의 여부
자바스크립트는 도달 가능성을 사용해 메모리 관리를 수행한다.
이때, 도달 가능한 값은 메모리에서 삭제되지 않는다.
그럼 도달 가능한 값에는 어떤 것들이 있을까?
이런 값은 루트(root)라 부른다.
전역 변수에 객체가 저장되어 있다고 가정하자.
이 객채의 프로퍼티가 또 다른 객체를 참조하고 있다면, 프로퍼티가 참조하는 객체는 도달 가능한 값이 된다. 이 객체가 참조하는 다른 모든 것들도 도달 가능하다고 여겨진다.
자바스크립트 엔진 내에선 가비지 컬렉터(garbage collector)가 끊임없이 동작한다.
가비지 컬렉터는 모든 객체를 모니터링하고, 도달할 수 없는 객체는 삭제한다.
let user = {
name: "Lee"
};
위 코드에서 전역변수 "user"
은 {name: "Lee"}
(줄여서 Lee)라는 객체를 참조한다. Lee의 프로퍼티 "name"은 원시값을 저장하고 있기 때문에 객체 안에 표현했다.
여기서, user
의 값을 다른 값으로 덮어쓰면 참조가 사라진다.
user = null;
이제 Lee는 도달할 수 없는 상태가 되었다.
그러면 가비지 컬렉터는 이제 Lee에 저장된 데이터를 삭제하고, Lee를 메모리에서 삭제한다.
참조를 user
에서 admin
으로 복사한 뒤 user
의 값을 다른 값으로 덮어써보자.
// user엔 객체 참조 값이 저장된다.
let user = {
name: "Lee"
};
let admin = user;
user = null;
이 경우에는 전역변수 admin
으로 여전히 객체 Lee
에 접근할 수 있기 때문에 Lee
는 메모리에서 삭제되지 않는다. admin
까지 덮어쓰인다면 Lee
는 삭제될 수 있다.
이제 좀 더 복잡한 예시를 보자.
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
메모리 구조는 아래와 같다. 지금은 모든 객체가 도달가능한 상태이다.
이제 참조 두 개를 지워보자.
delete family.father;
delete family.mother.husband;
삭제한 두 개의 참조 중 하나만 지웠다면, 모든 객체가 여전히 도달 가능한 상태였을 것이다.
하지만 참조 두 개를 지우면 John
으로 들어오는 참조는 모두 사라져 John
은 도달 가능한 상태에서 벗어난다.
💡 외부로 나가는 참조는 도달 가능한 상태에 영향을 주지 않는다!
외부에서 들어오는 참조만이 도달 가능한 상태에 영향을 준다.
가비지 컬렉션 후 메모리 구조는 다음과 같다.
객체들이 연결되어 섬 같은 구조를 만든다.
하지만 이 섬에 도달할 방법이 없는 경우,
섬을 구성하는 객체 전부가 메모리에서 삭제된다.
근원 객체 family
가 아무것도 참조하지 않도록 해보자.
family = null;
메모리 내부 상태는 다음과 같아진다.
🤷♂️ 이걸 지운다고? 쟤네끼리 연결되어 있잖아?
John과 Ann은 여전히 서로를 참조하고 있고, 두 객체 모두 외부에서 들어오는 참조를 갖고 있다.
하지만,
"family"
객체와 루트의 연결이 사라지면,
루트 객체를 참조하는 것이 아무것도 없게 된다.
섬 전체가 도달할 수 없는 상태가 되고,
섬을 구성하는 객체 전부가 메모리에서 제거된다.
객체를 새로운 객체와 오래된 객체로 나눈다.
🤔 나누는 기준은?
- 새로운 객체: 생성 이후 제 역할을 빠르게 수행해 금방 쓸모가 없어진 객체
- 오래된 객체: 일정 시간 이상 동안 살아남은 객체
가비지 컬렉터는 새로운 객체를 공격적으로 메모리에서 제거하고, 오래된 객체는 비교적 덜 감시한다.
방문해야할 객체가 많다면 모든 객체를 한 번에 방문하고 mark하는데 상당한 시간이 소모된다.
자바스크립트 엔진은 이런 현상을 개선하기 위해 가비지 컬렉션을 여러 부분으로 분리한 다음, 각 부분을 별도로 수행한다.
작업을 분리하고, 변경 사항을 추적하는 데 추가 작업이 필요하긴 하지만,
긴 지연을 짧은 지연 여러 개로 분산시킬 수 있다는 장점이 있다.
가비지 컬렉터는 실행에 주는 영향을 최소화하기 위해 CPU가 유휴 상태일 때에만 가비지 컬렉션을 실행한다.
이 외에도 다양한 최적화 기법과 가비지 컬렉션 알고리즘이 있다.
이 글은 https://ko.javascript.info/ 를 참고하여 작성하였습니다.