JS 파트 1) 4.3 : 가비지 컬렉션

나나·2021년 9월 17일
1
post-thumbnail

자바스크립트는 눈에 보이지 않는 곳에서 메모리 관리를 수행한다.

원시값, 객체, 함수 등 우리가 만드는 모든 것은 메모리를 차지한다.

🤔 그럼 이제 안 쓰는 것들은?

지금부터 자바스크립트 엔진이 어떻게 필요 없는 것을 찾아내 삭제하는지 알아보자.

가비지 컬렉션 기준

💡 도달 가능성(reachability)란?

어떻게든 접근하거나 사용할 수 있는지의 여부

자바스크립트는 도달 가능성을 사용해 메모리 관리를 수행한다.
이때, 도달 가능한 값은 메모리에서 삭제되지 않는다.

그럼 도달 가능한 값에는 어떤 것들이 있을까?

1. 태생부터 도달 가능한 값

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

이런 값은 루트(root)라 부른다.

2. 루트가 참조하는 값이나 체이닝으로 루트에서 참조할 수 있는 값

전역 변수에 객체가 저장되어 있다고 가정하자.

이 객채의 프로퍼티가 또 다른 객체를 참조하고 있다면, 프로퍼티가 참조하는 객체는 도달 가능한 값이 된다. 이 객체가 참조하는 다른 모든 것들도 도달 가능하다고 여겨진다.

자바스크립트 엔진 내에선 가비지 컬렉터(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-and-sweep

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

📌 자바스크립트 엔진이 적용하는 최적화 기법

1. generational collection(세대별 수집)

객체를 새로운 객체오래된 객체로 나눈다.

🤔 나누는 기준은?

  • 새로운 객체: 생성 이후 제 역할을 빠르게 수행해 금방 쓸모가 없어진 객체
  • 오래된 객체: 일정 시간 이상 동안 살아남은 객체

가비지 컬렉터는 새로운 객체를 공격적으로 메모리에서 제거하고, 오래된 객체는 비교적 덜 감시한다.

2. incremental collection(점진적 수집)

방문해야할 객체가 많다면 모든 객체를 한 번에 방문하고 mark하는데 상당한 시간이 소모된다.

자바스크립트 엔진은 이런 현상을 개선하기 위해 가비지 컬렉션을 여러 부분으로 분리한 다음, 각 부분을 별도로 수행한다.

작업을 분리하고, 변경 사항을 추적하는 데 추가 작업이 필요하긴 하지만,
긴 지연을 짧은 지연 여러 개로 분산시킬 수 있다는 장점이 있다.

3. idle-time collection(유휴 시간 수집)

가비지 컬렉터는 실행에 주는 영향을 최소화하기 위해 CPU가 유휴 상태일 때에만 가비지 컬렉션을 실행한다.

이 외에도 다양한 최적화 기법과 가비지 컬렉션 알고리즘이 있다.

이 글은 https://ko.javascript.info/ 를 참고하여 작성하였습니다.

profile
코린이의 둥당둥당 개발일지

0개의 댓글

관련 채용 정보