개요
의미
프로그램에서 더 이상 사용하지 않는 메모리를 자동으로 정리하는 것
특징
- 운영체제로부터 할당받은 메모리를 관리하는 역할
- 메모리 할당을 추적하고, 할당된 메모리 블록이 더이상 필요하지 않게 되었는지를 “스스로” 판단하여 필요하지 않다고 판단이 된다면 해당 메모리를 해제
- 프로그램 언어마다 가비지 컬렉션이 없을 수도 있음
- 고수준 언어: 가비지 컬렉션이 보통 있음
- 저수준 언어: 가비지 컬렉션이 없음
- 메모리 관리를 위해
malloc()
과 free()
를 사용해 개발자가 스스로 메모리를 할당하고 해제해야함
(참고) 고수준 언어와 저수준 언어
고수준 언어 | 저수준 언어 |
---|
인간 친화적인 언어 | 기계 친화적인 언어 |
컴파일러 또는 인터프리터를 사용하여 컴퓨터가 읽을 수 있는 기계어 코드로 변환해야 하며, 하드웨어와 직접 상호 작용하지는 않음 | 컴파일러나 인터프리터가 필요하지 않으므로 고수준 언어보다 빠른 편 |
인간이 이해하기 쉽고 다양한 작업을 수행하는 프로그램을 개발 | 레지스터 및 메모리와 직접 상호 작용을 할 수 있기 때문에 전반적으로 빠르게 실행되는 응용 프로그램을 빌드하는 데에 사용 |
메모리 생존 주기
(출처: 🧠 Memory leaks and Garbage Collection 🗑️ in JavaScript.)
메모리 할당(Memory Allocation)
개발자를 대신해서 JavaScript는 값이 선언될 때 자동으로 메모리를 할당함
할당된 메모리 사용(Memory Usage)
- 기본적으로 할당된 메모리를 읽고 쓰는 것을 의미
- 변수나 객체 속성의 값을 읽고 쓰거나, 함수 호출 시에 함수에 인수를 전달하여 수행하는 방식으로 발생
메모리 해제(Memory Release)
더이상 필요하지 않은 해제함
가비지 컬렉션의 알고리즘
선행 개념
- 참고(reference)
- 명시적이든, 암묵적이든 관계없이 메모리 관리 관점에서 어떤 객체가 다른 객체에 접근할 수 있다면 다른 객체를 참조한다고 말함
- 예를 들어서, JavaScript 객체는 자신의 프로토타입(prototype)에 대해 암묵적인 참조를 갖고 있고, 자신의 속성(property) 값에 대한 명시적 참조도 가지고 있음
- 객체를 참조한다는 의미
- 좁은 의미: 일반적인 JavaScript 객체를 참조함
- 넓은 의미: 일반적인 JavaScript 객체 뿐만 아니라 함수 스코프(function scope)나 글로벌 렉시컬 스코프(global lexical scope)까지도 포함
렉시컬 스코핑(lexical scoping)
변수 이름이 중첩된 함수에서 해석되는 방식을 정의하는 것으로, 중첩되어 있는 더 안쪽의 함수는 부모 함수가 값을 반환한 다음에도 부모 함수의 스코프를 포함하고 있음
레퍼런스 카운팅(참조 횟수 계산)
- 한 객체를 참조하는 변수의 수를 추적하는 방법으로 가장 단순한 형태의 가비지 컬렉션 알고리즘
- 객체를 참조하는 변수는 처음에는 특정 메모리에 대해 레퍼런스가 하나뿐이지만, 변수의 레퍼런스가 복사될 때마다 레퍼런스 카운트가 늘어남
- 객체를 참조하고 있던 변수의 값이 바뀌거나, 변수 스코프를 벗어나면 레퍼런스 카운트는 줄어듦
- 레퍼런스 카운트가 0이 되면, 그 객체와 관련한 메모리는 비울 수 있음
- 레퍼런스 카운트가 0이 된다는 말은 아무도 그 객체에 대한 레퍼런스를 가지고 있지 않다는 말과 동일함
- 순환 참조로 인한 문제가 생길 가능성이 높음
예시
function reference() {
var obj1 = {};
var obj2 = {};
obj1.p = obj2;
obj2.p = obj1;
}
reference();
- 위 코드에서는 두 객체가 생성되고 서로를 참조하고 있는 형태이기 때문에 순환 참조가 발생
- 이 객체들은 함수 호출 뒤에는 스코프를 벗어나게 되므로 실질적으로 쓸모없음
- 이들이 차지하던 메모리는 반환될 수 있지만, 레퍼런스 카운팅 알고리즘에서는 두 객체가 적어도 한 번은 참조한 것으로 간주되기 때문에 둘 다 가비지컬렉션이 될 수 없음
트레이싱
- 한 객체에 flag를 두고, 가비지 컬렉션 사이클마다 flag에 표시 후 삭제하는 mark and sweep 방법
- 객체에 in-use flag를 두고, 사이클마다 메모리 관리자가 모든 객체를 추적해서 사용 중인지 아닌지를 표시(mark)
- 그 후 표시되지 않은 객체를 삭제(sweep)하는 단계를 통해 메모리를 해제
- 현재 대부분의 가비지 컬렉션이 mark and sweep 알고리즘을 이용한 가비지 컬렉터를 장착
- 기준: 해당 객체에 닿을 수 있는지 (reachable)
- 레퍼런스 카운팅 방법보다는 나은 방법이라고 할 수 있음
- ‘참조받지 않는 객체’는 ‘닿을 수 없는 객체’기 때문에 가비지 컬렉션을 통해 메모리를 해제할 수 있기 때문
실행 방식
- 가비지컬렉터가 모든 루트의 완전한 목록을 만듦
루트(Roots)
- 일반적으로 루트는 코드에서 참조되는 전역 변수를 의미함
- ex. 자바스크립트에서 루트로 동작할 수 있는 전역 변수는
window
객체이며, Node.js에서 이와 동일한 객체는 global
-
모든 루트와 그 자식들을 검사해서 활성화 여부를 표시하고, 루트가 닿을 수 없는 것들은 가비지로 표시됨
-
가비지컬렉터가 활성으로 표시되지 않은 모든 메모리를 OS에 반환
메모리 누수
- 예상치 못한 참조로 인해 주로 발생
- 예상치 못한 참조는 개발자는 더 이상 사용되지 않을 것이라 생각했지만, 어떠한 이유로 활성화 상태인 루트 트리 안에 존재하는 메모리 조각들
- 자바스크립트에서 예상치 못한 참조는 더이상 사용되지 않지만 코드 상 어딘가에 유지되어 해제되지 못한 변수들
- 형태
- 우발적으로 생성된 전역변수
- DOM 외부에서의 참조
- 클로저의 잘못된 사용