Interview/Golang
Golang Garbage Collection(GC)
김 정출
2024. 10. 8. 22:13
Golang Garbage Collection(GC)
- Golang에서의 가비지 컬렉션(Garbage Collection, GC)은 자동 메모리 관리를 통해 사용되지 않는 메모리(즉, 더 이상 참조되지 않는 객체)를 회수하는 과정입니다.
- 이를 통해 개발자는 메모리 해제에 대해 신경 쓰지 않고 프로그램을 작성할 수 있습니다.
- Go의 가비지 컬렉션은 성능을 최대한 유지하면서도 메모리 누수를 방지하도록 설계되었습니다.
Golang의 GC 특징
- Concurrent Garbage Collector (동시 가비지 컬렉터)
- Go의
GC
는 stop-the-world 방식을 최소화하기 위해 동시성(concurrent)으로 동작합니다. 즉, 애플리케이션이 계속 실행되는 동안에도 가비지 컬렉션을 수행할 수 있습니다. 이로 인해 긴 시간 동안 프로그램이 멈추는 것을 방지하고, 실시간성을 요구하는 애플리케이션에서의 성능 저하를 줄입니다.
- Go의
- Tricolor Mark-and-Sweep Algorithm (삼색 마킹 및 스위프 알고리즘)
- Go의
GC
는 Mark-and-Sweep 알고리즘의 변형인 삼색 마킹(Tricolor Marking) 방식을 사용합니다. 이 방식은 메모리 객체를 3가지 색으로 구분하며, 가비지 컬렉션을 수행하는 과정에서 객체들이 어떤 상태에 있는지를 추적합니다.- White (흰색): 아직 검사되지 않은 객체로, 이 색의 객체들은
Garbage
로 간주될 수 있습니다. - Gray (회색): 검사 중인 객체로, 이 객체는 아직 참조하는 다른 객체들을 검사하지 않았습니다.
- Black (검은색): 완전히 검사된 객체로,
Garbage collection
에서 제외됩니다(즉, 이 객체는 유효한 객체임을 의미).
- White (흰색): 아직 검사되지 않은 객체로, 이 색의 객체들은
- Go의
- Generational Hypothesis 미사용
- 많은 현대 언어들이 사용하는 세대별 가비지 컬렉션(Generational GC) 기법은 Go에서는 채택되지 않았습니다. 이는 Go의 설계 철학 중 하나인 단순성과 동시성 처리를 고려한 결과입니다. Go는 세대별로 메모리를 관리하지 않고, 힙 메모리 전체를 대상으로 가비지 컬렉션을 수행합니다.
Golang 가비지 컬렉션의 동작 과정
- Golang의
GC
에서 객체가 더 이상 사용되지 않는지를 판단하는 기준은, 객체가 다른 객체나 프로그램의 루트에서 참조되지 않는 상태인지를 기준으로 합니다.
- Marking Phase (마킹 단계)
- 루트(root) 객체들(스택, 전역 변수 등)에서 시작해, 도달할 수 있는 모든 객체를 탐색합니다. 루트 객체란 전역 변수, 스택 변수, 레지스터, 그리고 Go 런타임이 관리하는 기타 루트 데이터를 포함합니다. 이 과정에서 유효한 객체들은 검은색으로 표시됩니다. 이때 동시에 프로그램이 실행 중일 수 있습니다.
- Sweeping Phase (스위프 단계)
- 마킹되지 않은, 즉 흰색으로 남은 객체들은
Garbage
로 간주되어 메모리에서 해제됩니다.
- 마킹되지 않은, 즉 흰색으로 남은 객체들은
- Write Barrier (쓰기 장벽)
- Go의 동시
Garbage Collector
는 쓰기 장벽(write barrier)을 사용하여 마킹 과정에서 발생할 수 있는 데이터의 불일치를 방지합니다. 쓰기 장벽은 메모리 상태가 변경될 때 발생하며, 객체 참조 관계의 변경을 트래킹해 마킹 과정에서의 오류를 방지합니다.
- Go의 동시
- Stop-the-world (STW)
Garbage collection
중 일부 작업(주로 Marking Phase의 시작과 끝)은 여전히 Stop-the-world(STW)를 필요로 합니다. 하지만 Go는 이 STW 시간을 최소화하도록 최적화되었습니다. 대부분의 가비지 컬렉션 작업은 프로그램과 동시(concurrent)로 수행됩니다.
참조 그래프(Reference Graph)
GC
에서 사용되지 않는 객체를 판단하는 핵심 개념은 참조 그래프(reference graph)입니다. Go 런타임은 프로그램의 메모리 상태를 객체 간의 참조 관계로 나타내며, 루트에서부터 시작해 객체들 간의 참조를 재귀적으로 추적하여 어떤 객체들이 유효한지를 판단합니다.
- 참조 관계: A 객체가 B 객체를 참조하고 있으면, B 객체는 A 객체가 유효할 때 유효합니다.
- 도달 가능성: 루트 객체에서 특정 객체까지 참조 체인을 통해 도달할 수 있는 객체는 유효한 객체로 간주됩니다.
- 도달 불가능성: 루트에서 더 이상 도달할 수 없는 객체는 프로그램 내에서 참조되지 않는 상태이므로 가비지 컬렉터가 이 객체를 회수할 수 있습니다.
- Graph를 기반으로 작동되기 때문에 순환 문제(Circular reference)도 해결됩니다.
Golang GC vs. free()
함수
- 자동화 vs. 수동화
- Golang: Go에서는 가비지 컬렉터가 메모리 해제를 자동으로 처리합니다. 프로그래머는 객체의 메모리를 해제하기 위한
free()
와 같은 함수를 명시적으로 호출할 필요가 없습니다. Go의 런타임이 객체가 더 이상 사용되지 않는다고 판단하면, 가비지 컬렉터가 이를 탐지하고 자동으로 메모리를 회수합니다. - C/C++ (
free()
): C나 C++에서는 프로그래머가 동적으로 할당한 메모리를 명시적으로free()
함수를 통해 해제해야 합니다. 메모리를 해제하지 않으면 메모리 누수(memory leak)가 발생할 수 있고, 잘못된 메모리 해제는 프로그램의 비정상 종료 등을 유발할 수 있습니다.
- Golang: Go에서는 가비지 컬렉터가 메모리 해제를 자동으로 처리합니다. 프로그래머는 객체의 메모리를 해제하기 위한
- 메모리 해제 시점
- Golang: Go의 가비지 컬렉션은 동시성(concurrent)으로 작동하며, 객체가 가비지로 간주되면 메모리를 해제하는데, 이 시점은 가비지 컬렉터가 필요할 때 스케줄링되어 발생합니다. 따라서 특정 객체가 언제 정확히 메모리에서 해제될지 프로그래머가 직접 결정할 수 없습니다. 가비지 컬렉터는 프로그램의 실행 흐름을 방해하지 않도록 최적화되어 있으며, 가능한 한 짧은 시간 동안 애플리케이션의 실행을 멈춥니다.
- C/C++ (
free()
):free()
를 호출하면, 즉시 해당 메모리가 해제됩니다. 따라서 메모리 관리 시점을 프로그래머가 완벽히 통제할 수 있습니다.
- 메모리 단편화 방지 및 효율성
- Golang: Go의 가비지 컬렉션은 메모리 단편화를 방지하고 효율성을 높이기 위해 압축(compaction) 작업을 동반할 수 있습니다. 메모리가 단편화되면 가비지 컬렉션 후에도 큰 연속된 메모리 공간을 확보하기 어려울 수 있으므로, 객체들을 이동시켜 메모리 공간을 최적화합니다.
- C/C++ (
free()
):free()
는 특정 메모리 블록만 해제하므로, 메모리 단편화를 방지하지 않습니다. 프로그래머는 단편화 문제를 해결하기 위한 추가적인 전략을 직접 설계해야 할 수 있습니다.
Golang의 GC 최적화 및 조정
- GOGC 환경 변수
- Go에서 가비지 컬렉션의 빈도는
GOGC
라는 환경 변수를 통해 조절할 수 있습니다. 기본적으로GOGC
값은 100으로 설정되어 있습니다. 이 값은 힙 크기가 100% 증가할 때 가비지 컬렉션이 실행된다는 의미입니다.- GOGC 값 감소: 가비지 컬렉션이 더 자주 발생합니다. 메모리 사용량을 줄일 수 있지만, CPU 사용량이 증가할 수 있습니다.
- GOGC 값 증가: 가비지 컬렉션이 덜 자주 발생합니다. 메모리 사용량은 증가할 수 있지만, CPU 사용량은 줄어듭니다.
- GOGC=off: 가비지 컬렉션을 비활성화합니다.
- Go에서 가비지 컬렉션의 빈도는
- Heap 관리
- Go는 메모리 할당 및 해제를 자동으로 관리합니다. 하지만 프로그램의 메모리 사용 패턴을 이해하고 필요에 따라
GOGC
나sync.Pool
을 이용해 메모리 재사용을 최적화할 수 있습니다.sync.Pool
은 Go에서 메모리 할당의 오버헤드를 줄이기 위해 제공하는 객체 풀링 기법입니다.
- Go는 메모리 할당 및 해제를 자동으로 관리합니다. 하지만 프로그램의 메모리 사용 패턴을 이해하고 필요에 따라
Golang 가비지 컬렉터의 성능 고려 사항
- GC pause: 가비지 컬렉션의 일시적인 멈춤(pause) 시간이 발생할 수 있지만, Go의 GC는 이 시간을 최소화하는 데 중점을 둡니다. 기본적으로 소프트 실시간(soft real-time) 가비지 컬렉터로 설계되어, 애플리케이션이 너무 긴 시간 동안 멈추지 않도록 최적화되어 있습니다.
- CPU 사용량: 가비지 컬렉션은 CPU 리소스를 소비합니다. GOGC 값을 통해 가비지 컬렉션 빈도를 조정하면서 메모리와 CPU 사용량의 균형을 맞출 수 있습니다.
Go의 가비지 컬렉션의 장단점
장점:
- 자동 메모리 관리로 인해 메모리 해제에 대한 프로그래머의 부담이 줄어듭니다.
- 동시 가비지 컬렉션 덕분에 GC로 인한 멈춤 시간이 최소화되어 실시간 시스템에서도 적합합니다.
- 삼색 마킹 방식으로 참조 순환 문제 없이 메모리 해제 가능.
단점:
- CPU 오버헤드: 가비지 컬렉션이 발생할 때 CPU 자원을 소모하여 성능에 영향을 줄 수 있습니다.
- 메모리 사용량 증가: GOGC 값에 따라 메모리 사용량이 증가할 수 있습니다.
결론
Golang의 가비지 컬렉션은 동시성과 최적화된 성능을 중시하는 방식으로 설계되었습니다. 프로그램 실행 중에도 가비지 컬렉션을 수행하여 실시간 성능을 저하하지 않도록 하며, 삼색 마킹을 통해 정확한 메모리 회수를 보장합니다. GOGC
값을 통해 가비지 컬렉션의 빈도를 조정하여 메모리와 CPU 사용의 균형을 맞출 수 있습니다.