javascript basics #3 오브젝트 가비지 콜렉션
자바스크립트에서의 메모리관리는 우리 눈에 보이지 않고 자동으로 수행된다. 우리가 만드는 primitive
, object
, function
은 다 메모리를 소비한다.
만일 우리가 만든 primitive
, object,
function
과 같은 것들 중에 더이상 필요가 없어진 것들은 어떻게 될까? 자바스크립트 엔진은 어떻게 이를 알고 정리할까?
자바스크립트 메모리관리의 핵심은 도달가능성(Reachability)이다. 간단히 말하자면 "도달 가능한(Reachable)
" 값들은 접근 가능하거나 언젠가 써먹을 수 있다. 도달 가능한(Reachable)
값들은 메모리에 저장된다고 보장할 수 있다.
만일 root로부터 참조에 의해 혹은 참조의 체인에 의해 접근 가능하면, 도달 가능하다(Reachable)
고 판단한다.
이를테면, 전역 영역에 오브젝트가 하나 있는데, 다른 오브젝트를 참조하는 프로퍼티를 갖고 있다면, 그 다른 오브젝트는 접근 가능한(Reachable)
오브젝트라고 여겨질 것이다.
이러한 자바스크립트 엔진 내부의 백그라운드 프로세스를 garbage collector
라고 부른다. garbage collector
는 모든 오브젝트를 모니터링한다. 그리고 접근 불가능한(Unreachable)
오브젝트가 생길 때 그 오브젝트를 제거해준다.
let person = {
name: "Jake Seo"
};
위와 같이 만들면 "name"
이라는 프로퍼티는 "Jake Seo"
라는 primitive
를 가리킨다.
person = null;
만일 우리가 위와 같이 person
오브젝트가 참조하는 것을 null
로 바꾼다면, 기존에 만들었던 person
오브젝트는 어디에서도 참조되지 않는다.
이 때, 자바스크립트 엔진의 garbage collector
는 참조되지 않는 오브젝트를 삭제하고 메모리공간을 확보할 것이다.
우리가 person
이라는 참조의 주소를 developer
라는 참조로 옮겼을 때를 가정해서 생각해보자.
let person = {
name: "Jake Seo"
};
let developer = person;
위의 간단한 예제의 경우와 똑같이
person = null;
이렇게 person
에 null
을 할당한다면, garbage collection
은 일어날까? 정답은 안 일어난다.
위의 코드 결과와 같이 아직 developer
라는 변수가 오브젝트를 가리키고 있기 때문이다. 만일 developer
에도 똑같이 null
을 할당한다면 그 때는 garbage collection
이 일어날 것이다.
여기 더욱 복잡한 예제가 있다.
function marry(man, woman) {
woman.husband = man;
man.wife = woman
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
함수 marry
는 두 오브젝트가 서로를 참조하게 만들고, 두 오브젝트를 포함한 하나의 새로운 오브젝트를 만든다.
현재까지는 모든 오브젝트가 접근 가능한(Reachable)
상태이다.
다음 두가지 참조를 지우면 어떻게 될까?
delete family.father;
delete family.mother.husband;
이렇게 되면 { name : "John" }
오브젝트를 참조하는 오브젝트가 사라진다. 참조되지 않는 오브젝트는 자동으로 삭제된다.
밖으로 나가는 참조와 상관없이 내부로 들어오는 참조가 사라지면 해당 오브젝트는 더 이상 접근 불가능한(Unreachable)
오브젝트로 가정하고 삭제된다.
garbage collection
이 끝난 결과는 다음과 같다.
내부적으로 서로 참조하는 오브젝트가 한번에 접근 불가능하게 변할 수도 있다. 다음의 코드를 수행했다고 가정해보자.
let family = null;
기존에 family
가 참조하던 오브젝트의 메모리 주소는 어디에서도 접근 불가능한(Unreachable)
상태가 되므로 전부 삭제된다.
이러한 예제는 접근 가능성(Rechability)
의 개념이 얼마나 중요한지를 설명한다.
가장 기본적인 garbage collection
알고리즘은 "mark-and-sweep(체크하고 훑기)"
이라 불리운다.
다음의 garbage collection
단계들이 정기적으로 행해진다.
garbage collector
는 최상위에 있는 root
들에 접근하고 그들을 전부 "mark"
한다.garbage collector
는 root
로부터 연결되는 모든 참조들에 방문하고 "mark"
한다.garbage collector
는 "mark"
된 모든 오브젝트에 방문하고 그 오브젝트들의 참조를 "mark"
한다. 방문된 모든 오브젝트는 2번 이상 방문하지 않도록 반드시 기억한다.접근 가능한(reachable)
참조에 방문할 때까지 계속 방문한다."mark"
되지 않은 오브젝트는 삭제한다.root
로부터 아주 커다란 페인트를 쏟는 장면을 상상할 수 있다. 페인트가 묻지 않은 오브젝트들은 지워진다.
그리고 자바스크립트 엔진은 이외에도 많은 최적화 방법을 통해 자바스크립트 코드 수행에 영향이 없도록 이 과정을 더욱 빠르게 수행한다.
다음과 같은 몇가지 최적화 방법이 있다.
"새 오브젝트"
과 "오래된 오브젝트"
로 나뉜다. 많은 오브젝트가 나타나고 주어진 일을 하고 빠르게 죽는다. 이러한 오브젝트들은 매우 공격적으로 정리될 수 있다. 오랫동안 살아남은 오브젝트는 "오래된 오브젝트"
분류에 속하게 되고, 가비지 콜렉션에 의한 체크를 덜 받는다."mark"
하려한다면, 실행하는데에 어느정도 시간이 걸리고 어느정도는 눈에 보이는 딜레이가 발생할 것이다. 그래서 자바스크립트 엔진은 garbage collection
을 한번에 하는 것이 아니라 여러번 나눠서 진행한다. 나눠진 garbage collection
과정은 하나하나씩 독립적으로 수행된다. 이러한 과정은 변화를 추적하기 위해 추가적인 기록이 요구되지만, 큰 하나의 딜레이 대신에 작은 여러번의 딜레이를 가질 수 있다.garbage collector
는 실행에 미치는 영향을 줄이기 위해서, CPU가 휴지기(idle
)일 때 만 동작한다.garbage collection
알고리즘에는 다른 최적화들도 많이 존재한다. 많은 자바스크립트 엔진들이 있고 각각의 자바스크립트 엔진들은 다르게 구현되어 있다. 그리고 훨씬 더 중요한 것은 엔진이 개발되면서 garbage collection
의 방법은 바뀌기 마련이다. 그러므로 지식이 필요하지 않은 상태에서 미리 깊게 공부하는 것은 때론 도움이 되지 않을 수 있다.
garbage collection
은 자동으로 동작한다. 우리가 강요하거나 막을 수 없다.접근 가능한(Reachable)
한 계속 메모리에 남아있는다.접근 가능한(Reachable)
한 것과는 다르다. 내부적으로 서로를 참조하는 오브젝트가 있다 하더라도 root
로부터 접근이 불가능하다면 위의 접근 불가능한 섬
의 예시처럼 될 수 있다.현대 자바스크립트 엔진에는 더욱 진보된 알고리즘이 사용되고 있다.
low-level
프로그래밍에 익숙하다면, V8 garbage collector
에 대한 글인 A tour of V8: Garbage Collection에 더욱 자세한 내용이 있다.
V8 blog도 역시 메모리 관리법에 대한 글을 쓴다. V8 엔지니어인 Vyacheslav Egorov의 블로그도 도움이 될 것이다.
low-level
최적화에 관심이 갈 때 garbage collection
최적화에 대한 글을 읽으면 도움이 될 것이다.