최근 면접에서 가비지 컬렉션에 대한 질문을 받았다. 동적 메모리 할당을 공부할 적에 잠깐 본 적이 있는 주제라 기초적인 수준의 답변은 했으나, 짧은 경험상 GC에 대해서 제대로 들여다본 적이 없었다는 생각이 들어 아쉬웠다.
특히 '자바스크립트에서도 GC가 존재하는가?' 라는 질문을 주셨을 때, 자바스크립트를 다뤘으면서도 한번도 이를 고민해본 적이 없다는 점을 반성하게 되었다. '코드를 짜면서 free를 해준 기억은 없기에 엔진 내부적으로 GC가 동작하지 않을까 생각한다' 정도의 답변을 했으나, 이 기회에 제대로 알아야겠다는 생각을 하게 되었다. 해당 내용은 아래 포스팅에 마저 기록!
가비지 컬렉션(GC)은 동적 메모리 관리의 한 형태로, 프로그램이 할당했던 메모리 영역 중에서 더 이상 사용되지 않는 부분을 자동으로 찾아내서 해제하는 기능이다. Java, JavaScript, Python 등 고수준 언어들은 기본적으로 GC를 지원한다.
가비지 컬렉션이 없는 언어(예를 들어 C)에서는 동적 메모리 할당을 해주고 더 이상 접근할 필요가 없을 때는 꼭 free를 해주어야 한다. 그렇지 않으면 점차 memory leak이 발생하고 런타임 동안 점차 애플리케이션 성능이 저하되기 때문이다. (TMI 지만 컴퓨터 재부팅은 이렇게 누수된 자원들을 다시 회수하기 때문에 실제로 효과있는 방법이다.)
가비지 컬렉션 방식의 대표적인 방식으로는 1) 마크-앤-스위프, 2) 레퍼런스 카운팅이 있다.
가비지 컬렉션의 가장 기본적인 알고리즘 중 하나는 '마크-앤-스위프(Mark-and-Sweep)'이다.
'마크' 단계는 프로그램의 메모리에서 사용 중인 객체를 식별하는 과정이다.
이 과정은 다음과 같이 진행된다: 루트 식별 -> 탐색 & 마킹
Q. 루트 집합?
[루트 집합]
GC 과정에서 '루트(Root)'는 GC가 메모리에서 어떤 객체들을 살리고 어떤 객체들을 가비지로 간주해야 할지 판단하는 데 있어 시작점으로 삼는 객체들을 의미한다. 가비지 판별 여부는 '루트 객체부터 시작해서 해당 객체까지 접근 가능한가? '이다.
루트에 속하는 객체의 예시
- 전역 변수: 전역 스코프에 선언된 변수 또는 객체는 어디서든 접근할 수 있으므로, GC에 의해 루트로 간주된다.
- 현재 호출 스택에 있는 함수의 지역 변수와 매개변수는 실행 컨텍스트가 활성화되어 있는 동안에는 루트로 간주된다. 이는 함수 내부에서 생성하거나 사용하는 객체들이 가비지 컬렉션의 대상이 되지 않도록 보호하기 위함이다.
- 내부 시스템에서 참조하는 객체: 자바스크립트 엔진이나 호스팅 환경(예: 웹 브라우저)에서 내부적으로 사용하는 객체도 루트에 포함될 수 있다. 예를 들어, 웹 API 호출에 의해 생성된 콜백 함수나 이벤트 리스너 객체들이 이에 해당될 수 있다.
이러한 루트 집합을 시작점으로 하여, 가비지 컬렉터는 참조 체인을 따라가며 접근 가능한 모든 객체를 탐색하고 마크한다. 이 과정에서 루트로부터 직접적이거나 간접적으로 접근할 수 없는 객체들은 더 이상 사용되지 않는 것으로 간주되어 가비지 컬렉션의 대상이 된다.
루트 집합이 크고 복잡할수록 마킹 과정에서 검사해야 할 객체의 수가 늘어나므로, 가비지 컬렉션의 오버헤드가 증가할 수 있다. 따라서, 가비지 컬렉션 최적화의 한 방법은 루트 집합의 크기와 복잡성을 최소화하는 것이다.
이 '마크' 단계를 통해, 가비지 컬렉터는 사용 중인 객체와 사용되지 않는 객체(가비지)를 구분할 수 있게 된다. 이후 '스위프(Sweep)' 단계에서는 마킹되지 않은 객체들, 즉 사용되지 않는 객체들을 메모리에서 해제한다.
그 다음 '스위프' 단계에서는 힙 메모리 전체를 스캔하여 마킹되지 않은 객체들을 가비지로 간주하고 메모리를 해제한다.
스위프 단계의 작업은 다음 단계를 거친다:
마크-앤-스위프 장단점
Reference counting은 객체가 다른 객체로부터 몇 번 참조되는지 카운트하는 방식이다. 어떤 객체의 참조 횟수가 0이 되면, 즉 다른 어떤 객체도 그 객체를 참조하지 않게 되면 그 객체는 가비지로 간주되어 메모리가 해제된다.
이 방식의 핵심 원리는 간단하다. '객체에 대한 참조가 생성될 때마다 참조 카운트를 증가, 참조가 제거될 때마다 카운트를 감소 -> 객체의 참조 카운트가 0이 되면, 그 객체는 더 이상 필요하지 않은 것으로 간주되어 메모리 해제'
작동 과정은 다음과 같다:
참조 카운팅의 장단점
순환 참조 해결 방안
순환 참조 문제를 해결하기 위한 일반적인 방법 중 하나는 약한 참조(Weak Reference)를 사용하는 것이다. 약한 참조는 참조 카운트에 영향을 주지 않으므로, 순환 참조가 발생해도 하나의 객체가 가비지 컬렉션의 대상이 될 수 있게 한다.
위 내용들을 종합해서 보면, 확실히 가비지 컬렉션에도 유의해야할 단점들이 있다.
JavaScript | V8 엔진의 메모리 관리 (0) | 2024.03.27 |
---|---|
JavaScript | 실행 컨텍스트 (1) - 실행 컨텍스트, 호이스팅, 스코프 (0) | 2024.03.26 |
JavaScript | 스프레드 연산자, forEach, map 기본 개념 (1) | 2024.01.09 |
JavaScript | JS 엔진과 런타임 환경의 구조 (스택, 힙, 이벤트루프 ..) (0) | 2024.01.04 |
JavaScript | 비동기 처리 | async & await 키워드 (1) | 2024.01.01 |