참조가 아니라 도달이다. (feat:가비지 컬렉션)

houndhollis·2025년 8월 11일
4

자스시 메모리구 오늘의 청소달인 32세 가컬씨

개발을 하다 보면 OOM(Out of Memory)이나 메모리 릭과 같은 문제를 종종 마주하게 됩니다.
이럴 때, 우리를 조용히 도와주는 존재가 있다는 걸 알고 계셨나요?
바로 가비지 컬렉션(Garbage Collection)입니다.

우리가 개발하면서 다루는 원시값, 객체, 함수등은 모두 메모리를 차지합니다.
더는 쓸모가 없어진 것들은 어떻게 되는걸까요? 가비지 컬렉션에 대해서 알아봅시다!

💥 참조인줄 ... 알았습니다

이번에 새롭게 학습을 하면서 저는 여태까지 참조하지 않으면 가비지 컬렉션이 메모리 할당을 해제하고 청소를 하는줄 알았습니다. 하지만 그것은 잘못된 생각이였습니다.

자바스크립트의 가비지 컬렉션은 "참조 여부"가 아닌, "도달 가능성(reachability)"에 따라 동작합니다.

도달 가능성이란 어디서든 접근할 수 있는 값을 뜻합니다.
《코어 자바스크립트》 에 나오는 도달 가능성의 예시입니다.

  • 현재 함수의 지역 변수와 매개변수
  • 중첩 함수의 체인에 있는 함수에서 사용되는 변수와 매개변수
  • 전역 변수
  • 기타 등등

이런 값들은 일반적으로 루트(root)라고 불리며,
루트에서 참조할 수 있는 값들은 도달 가능한 값으로 간주되어 GC의 대상에서 제외됩니다.

즉, 자바스크립트 엔진 내의 가비지 컬렉터는 계속해서 객체들의 참조 상태를 감시하고,
어디에서도 도달할 수 없는 값이 되면 메모리에서 정리해 버립니다.

간단 예시

간단한 예시를 작성해 보겠습니다.


let woong = {
  attribute : "kawai"
}

해당 객체를 global 에서 본다면 전역 변수 woong{attribute: "kawai"} 라는 객체를 참조합니다.

만약 여기서 woong의 값을 다른 값으로 재할당하면,

woong = null

이제 Kawai 는 도달할 수 없는 상태가 되었으며, 접근할 방법조차 없어집니다. 이러한 순간 가비지 컬렉터는 데이터를 삭제하고 메모리에서도 청소를 해버립니다.

도달할 수 없는 섬

객체들이 연결되어 섬 같은 형태를 만드는데 도달할 방법이 없는 경우를 말합니다.

function link(server, client) {
  client.connectedServer = server;
  server.connectedClient = client;

  return { server, client };
}

let network = link(
  { id: "Server" },
  { id: "Client" }
);

// 네트워크 세션 종료
network = null;

// Server와 Client는 여전히 서로를 참조하지만,
// 외부에서 접근할 방법이 없음 → 도달할 수 없는 섬 → GC가 수거

해당 개념이 얼마나 중요한지 보여줍니다. Server와 Client는 서로를 참조하고 있지만, "network" 객체와 루트의 연결이 사라지면 루트 객체를 참조하는 것이 아무것도 없어지며 섬 전체가 도달할 수 없는 상태가 되고 객체 전부가 메모리에서 제거가 됩니다.

내부 알고리즘

"mark-and-sweep" 이라 불리는 알고리즘으로 동작하게 됩니다.
다음 단계에 따라 수행하게 됩니다.

  • 가비지 컬렉터는 루트(root) 정보를 수집하고 이를 mark 합니다.
  • 모든 객체를 방문하고 이것들을 mark 합니다.
  • 한번 방문한 객체는 전부 mark 하기 때문에 같은 객체를 다시 방문하는 일은 없습니다.
  • 루트에서 도달 가능한 모든 객체를 방문할 때까지 위 과정을 반복합니다.
  • mark 되지 않은 객체를 메모리에서 삭제합니다.

도달 가능한 모든 객체를 방문할 때까지, mark한 객체가 참조하는 객체를 계속해서 mark 를 하게됩니다.

이후 방문할 수 없는 객체를 메모리에서 삭제하게 됩니다.
사진을 보면 이해하기 쉽습니다!

자바스크립트 엔진은 실행에 영향을 미치지 않으면서 다양한 최적화 기법을 사용합니다.

- generational collection (세대별 수집) - 새로운 객체와 오래된 객체로 나눕니다, 객체의 상당수는 빠르게 쓸모 없어지는데, 이런 객체를 새로운 객체 로 구분합니다. 이런 객체를 공격적으로 제거하고, 일정 시간 살아남은 객체는 오래된 객체로 덜 감시합니다.

- incremental collection(점진적 수집) - 방문해야 할 객체가 많다면 모든 객체를 한 번에 방문하는데 시간이 많이소요 됩니다. 이럴 경우 실행 속도가 눈에 띄게 느려지게 됩니다. 자바스크립트 엔진은 해당문제를 개선하기 위해 가비지 컬렉션을 여러 부분으로 분리한 다음, 별도로 작업을 실행하여 긴 지연을 짧은 지연, 즉 여러 개로 분산시킬 수 있다는 장점이 있습니다.

- idle-tiem-collection(유휴 시간 수집) - 가비지 컬렉터는 실행에 주는 영향을 최소화 하기 위해 CPU가 유휴 상태일 때에만 실행됩니다.

const [list, setList] = useState([]);
// 잘못된 패턴
useEffect(() => {
  const timer = setInterval(() => {
    setList(prev => [...prev, { data: new Array(100000).fill(0) }]);
  }, 10); // 10ms마다 10만개짜리 배열 생성
  return () => clearInterval(timer);
}, []);

🤔 GC가 mark 하는 것 보다 객체 생성이 더 많으면 OOM이 발생하겠군요?

10ms마다 큰 배열을 계속 생성해 상태에 쌓기 때문에 메모리 사용량이 급증하고, 가비지 컬렉터가 처리하기 어려워져 메모리 문제를 일으킬 수 있습니다.

요약

  • 가비지 컬렉션은 엔진이 자동으로 동작하기 때문에 억지로 실행하거나 막을 수 없습니다.
  • 객체는 도달 가능한 상태일 때 메모리에 남습니다.
  • 참조된다고 해서 메모리에서 살아남는 것이 아닙니다. 도달이 가능해야 합니다.
profile
한 줄 소개

3개의 댓글

comment-user-thumbnail
2025년 8월 14일

참조가 아니라 도달 가능성이 GC 기준이라는 설명이 명확해서 좋았습니다!
실제로 JS에서 Map은 직접 삭제하지 않으면 메모리 릭이 생길 수 있고, 말씀하신 것처럼 WeakMap은 키 객체가 외부에서 도달할 수 없어진다면 자동으로 GC처리가 되는 것 같더라구요!
실제로 Emotion 라이브러리에서도 캐시 관리를 위해 GC를 고려해서 WeakMap을 사용하는 것으로 알고 있습니다!
덕분에 JS의 GC 동작 원리에 대해서 잘 알고 갈 수 있었습니다~!!!

답글 달기
comment-user-thumbnail
2025년 9월 1일

가비는 싫은데 가비지컬렉터는 좋네요! 이렇게 별 생각 없이 쓰던것들의 원리를 알아가는 포스팅 좋습니다!

답글 달기
comment-user-thumbnail
2025년 9월 16일

저도 어렴풋이 '참조가 없으면 GC가 정리해준다'고 생각해왔던 개념을 구체적으로 알 수 있었던 기회였네요. 잘 읽고 갑니다 ㅎㅎ!

답글 달기