자바스크립트는 눈에 보이지 않는 곳에서 메모리 관리를 수행한다. 원시값, 객체, 함수 등 우리가 만드는 모든 것은 메모리를 차지한다. 자바스크립트 엔진은 더는 쓸모 없는 것들을 어떻게 처리하는지 알아보자.
가비지 컬렉션 기준
자바스크립트는 도달 가능성(reachability)이라는 개념을 사용하여 메모리 관리를 수행한다.
'도달 가능한(reachable)'값은 쉽게 말해 어떻게든 접근하거나 사용할 수 있는 값을 의미한다. 도달 가능한 값은 메모리에서 삭제되지 않는다.
태생부터 도달 가능하기 때문에, 명백한 이유 없이 삭제되지 않는 값 (root)
- 현재 함수의 지역 변수와 매개변수
- 중첩 함수의 체인에 있는 함수에서 사용되는 변수와 매개변수
- 전역 변수
- 기타 등등
이런 값을 루트(root)라고 부른다.
루트가 참조하는 값이나 체이닝으로 루트에서 참조할 수 있는 값은 도달 가능한 값
- 전역 변수에 객체가 저장되어 있을 때, 이 객체의 프로퍼티가 또 다른 객체를 참조하고 있다면, 그 프로퍼티가 참조하는 객체는 도달 가능한 값이 된다.
자바스크립트 엔진 내에서는 가비지 컬렉터가 끊임없이 동작한다. 가비지 컬렉터는 모든 객체를 모니터링하고, 도달할 수 없는 개체는 삭제한다.
let user = {
name: "John"
}
이 그림에서 화살표는 객체 참조를 나타낸다. 전역 변수user
는 {name: "John"}
이라는 객체를 참조한다.user
의 값을 다른 값으로 덮어버리면 참조(화살표)가 사라진다.
user = null
이제 {name: "John"}
에 도달할 수 없는 상태가 되었다. 가비지 컬렉터는 이제 저장된 데이터({name: "John"}
)를 삭제하고 메모리를 삭제한다.
참조 두 개
참조를 user
에서 admin
으로 복사했다고 가정.
// user엔 객체 참조 값이 저장된다.
let user = {
name: "John"
};
let admin = user;
user = null;
user
의 값을 다른 값으로 덮어써도 John 객체에 접근이 가능하여 메모리에서 삭제되지 않는다. 그러나 이 상태에서 admin
을 다른 값으로 덮어쓰면 John 객체는 메모리에서 삭제 된다.
연결된 객체
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
marry
는 매개변수로 받은 구 객체를 서로 참조하여, 두 객체를 포함시키는 새로운 객체를 반환한다.
지금은 모든 객체가 도달 가능한 상태이다.
이제 참조 두 개를 지워볼 것이다.
delete family.father;
delete family.mother.husband;
삭제한 두 개의 참조 중 하나만 지웠다면, 여전히 도달 가능한 상태였겠지만, 참조 가능한 두 참조를 지우면 John으로 들어오는 참조는 모두 사라져서 도달 가능한 상태에서 벗어나게 된다.
외부로 나가는 참조는 도달 가능한 상태에 영향을 주지 않기 때문에 메모리에서 제거된다. John에 저장된 데이터(프로퍼티) 또한 메모리에서 삭제된다. 가비지 컬렉션 후 메모리 구조는 다음과 같다.
도달할 수 없는 섬
객체들이 연결되어 섬 같은 구조를 만드는데, 참조할 수 없는 섬의 경우 객체 전부가 메모리에서 삭제가 된다.
family = null;
family
객체와 루트의 연결이 사라지면 루트 객체를 참조하는 것이 아무것도 없게 되어, 도달할 수 없는 상태가 되어 섬을 구성하는 객체 전부가 메모리에서 제거된다.
내부 알고리즘
'mark-and-sweep'이라 불리는 가비지 컬렉션 기본 알고리즘
- 가비지 컬렉터는 루트(root) 정보를 수집하고 이를 'mark(기억)'한다.
- 루트가 참조하고 있는 모든 객체를 방문하고 이것들을 'mark'한다.
- mark된 모든 객체에 방문하고 그 객체들이 참조하는 객체도 mark한다. 한번 방문한 객체는 전무 mark하기 때문에 같은 객체를 다시 방문하지 않는다.
- 루트에서 도달 가능한 모든 객체를 방문할 때까지 위 과정을 반복합니다.
- mark 되지 않은 모든 객체를 메모리에서 삭제한다.
오른편에 있는 '도달할 수 없는 섬'을 어떻게 제거하는지 알아보자.
첫 번째 단계에서 루트를 mark 한다.
루트가 참조하고 있는 것들을 mark 한다.
도달 가능한 모든 객체를 방문할 때까지, mark 한 객체가 참조하는 객체를 계속해서 mark 한다.
방문할 수 없었던 객체를 메모리에서 삭제한다.
루트를 시작으로 참조를 따라가다 도달가능한 객체 모두 마크가 되면서 마크 되지 않은 객체는 메모리에서 삭제된다.
자바스크립트 엔진은 실행에 영향을 미치지 않으면서 가비지 컬렉션을 더 빠르게 하는 다양한 최적화 기법을 적용한다.
최적화 기법
Generational Collection(세대별 수집)
객체를 '새로운 객체'와 '오래된 객체'로 나뉜다. 객체 상당수는 생성 이후 제 역할을 빠르게 수행해 금방 쓸모가 없어지는데, 이런 객체를 '새로운 객체'로 구분한다. 가비지 컬렉터는 이런 객체를 공격적으로 메모리에서 제거한다. 일정 시간 이상 동안 살아남은 객체는 '오래된 객체'로 분류하고, 가비지 컬렉터가 덜 감시한다.
Incremental Collection(점진적 수집)
방문해야 할 객체가 많다면 모든 객체를 한 번에 방문하고 mark 하는데 많은 시간이 소모된다. 자바스크립트 엔진은 이런 현상을 개선하기 위해 가비지 컬렉션을 여러 부분으로 분리한 다음, 각 부분을 별도로 수행한다. 작업을 분리하고, 변경 사항을 추적하는데 추가 작업이 필요하지만, 긴 지연을 짧은 지연 여러 개로 분산 시킬 수 있다는 장점이 있다.
Idle-Time Collection(유휴 시간 수집)
가비지 컬렉터는 실행에 주는 영향을 최소화하기 위해 CPU가 유휴 상태일 때에만 가비지 컬렉션을 실행한다.
요약
- 가비지 컬렉션은 엔진이 자동으로 수행하므로 개발자는 이를 억지로 실행하거나 막을 수 없다.
- 객체는 도달 가능한 상태일 때 메모리에 남아 있다.
- 참조된다고 해서 도달 가능한 것은 아닙니다. 서로 연결된 객체들도 도달 불가능 할 수 있다.
참고
'JavaScript' 카테고리의 다른 글
유데미(Udemy) "NodeJS 완벽 가이드" 수강 (0) | 2024.03.31 |
---|---|
[JS] V8: 전체 컴파일러 (1) | 2023.04.20 |
[Js] V8 가비지 컬렉션 - 1/2 (0) | 2023.04.12 |
[Js RoadMap] 01. JavaScript가 뭔데? (0) | 2023.03.27 |