표시하고 쓸기 알고리즘
크리스는 가비지 콜렉터(GC)가 "도달 가능성(Reachability)"을 기준으로 메모리를 관리한다는 것을 알았다.
하지만 궁금증이 또 생겼다.
"메모리 전체를 뒤져서 도달 가능한지 아닌지 확인하려면 시간이 꽤 걸릴 텐데? 게임을 하고 있는데 청소부가 갑자기 나타나서 '잠깐! 나 청소 좀 할게' 하고 1초 동안 화면을 멈춰버리면 어떡해?"
실제로 과거의 GC는 청소를 위해 프로그램 실행을 잠시 멈추는 "Stop-the-world" 현상이 심했다.
하지만 현대의 브라우저(V8 엔진 등)는 사용자가 눈치채지 못할 만큼 빠르고 똑똑하게 청소를 수행한다. 그 기반이 되는 알고리즘이 바로 **표시하고 쓸기(Mark-and-Sweep)**다.
1. 페인트통 들고 순찰하기
이 알고리즘의 동작 방식은 페인트 칠하기로 비유할 수 있다.
GC는 주기적으로 페인트통을 들고 메모리 영역을 순찰한다.
1단계: 표시하기 (Mark)
2단계: 쓸어버리기 (Sweep)
2. 순환 참조의 종말
지난 시간에 참조 카운팅(Reference Counting) 방식이 해결하지 못했던 '고립된 섬(순환 참조)' 문제를 기억하는가?
JavaScript
// 서로 참조하지만, 루트에서는 연결이 끊긴 상태 objA.friend = objB; objB.friend = objA; root.data = null; // 연결 끊음
Mark-and-Sweep 알고리즘 앞에서는 이들도 짤없다.
이 알고리즘 덕분에 자바스크립트 개발자들은 더 이상 순환 참조 메모리 누수 공포에 시달리지 않게 되었다.
3. 최적화: 청소도 요령껏 (Generational Collection)
"그래도 메모리가 몇 GB나 되면 다 훑는 데 오래 걸리지 않을까?"
맞다. 그래서 V8 같은 최신 엔진은 무식하게 매번 전체를 훑지 않는다. **세대별 가비지 콜렉션(Generational Garbage Collection)**이라는 고급 기술을 쓴다.
가설: "대부분의 객체는 금방 죽는다."
함수 안에서 잠깐 쓴 변수나 임시 객체들은 만들자마자 할 일을 다하고 금방 쓰레기가 된다. 반면, 전역 설정이나 코어 데이터는 프로그램이 끝날 때까지 살아남는다.
엔진은 메모리 영역을 두 개로 나눈다.
이렇게 하면 GC는 대부분의 시간을 크기가 작은 '새로운 세대'만 빠르게 훑고 지나가기 때문에, **브라우저 멈춤 현상(Freezing)**을 최소화할 수 있다.
4. 우리가 할 일은?
"엔진이 이렇게 똑똑하면 개발자는 아무것도 안 해도 되나?"
아쉽게도 완벽하지는 않다. GC는 "참조가 남아있는 한" 절대 지우지 않는다.
우리가 실수로 불필요한 참조를 남겨두면, GC는 "아, 개발자가 이거 쓰는구나"라고 오해하고 영원히 메모리에 남겨둔다. 이것이 바로 **메모리 누수(Memory Leak)**다.
개발자가 흔히 저지르는 실수로 인해 GC가 청소를 못 하는 대표적인 시나리오들을 알아야 내 앱을 지킬 수 있다.
7장의 마지막 주제, "[JavaScript] 그 밖의 메모리 누수 시나리오: 개발자의 실수들" 편에서 계속된다.