Go 언어의 청소부, 가비지 컬렉터 파헤치기: 메모리 관리, 이젠 맡겨주세요!

Go 언어의 청소부, 가비지 컬렉터 파헤치기: 메모리 관리, 이젠 맡겨주세요!

들어가며: "쓰레기는 알아서 척척!" Go 언어의 똑똑한 메모리 관리 비법

우리가 집안 청소를 하듯, 프로그래밍에서도 더 이상 사용하지 않는 '메모리 쓰레기'를 깔끔하게 치워주는 역할이 필요합니다.

그렇지 않으면 메모리가 점점 쌓여서 프로그램이 느려지거나 멈춰버리는 불상사가 생길 수 있거든요.

Go 언어에는 바로 이 '메모리 청소'를 자동으로 해주는 아주 똑똑한 시스템, 바로 '가비지 컬렉터(Garbage Collector, GC)'가 내장되어 있습니다.

이번 글에서는 Go 언어의 가비지 컬렉터가 어떻게 작동하는지, 그리고 어떤 특별한 점들을 가지고 있는지 쉽고 재미있게 알아보겠습니다.

마치 우리 집 로봇 청소기처럼, Go 언어의 GC가 얼마나 효율적으로 메모리를 관리하는지 함께 살펴볼까요?

Go 언어 가비지 컬렉터의 3가지 핵심 설계 원칙!

Go 언어의 가비지 컬렉터는 다른 프로그래밍 언어들과는 조금 다른, 특별한 설계 원칙들을 가지고 있습니다.

이 원칙들 덕분에 Go 프로그램은 빠르고 효율적으로 작동할 수 있는 것이죠.

  1. 비세대적 (Non-generational): 몇몇 언어들은 메모리에 있는 객체들을 '나이'에 따라 젊은 세대, 늙은 세대 등으로 나누어 관리합니다.

    하지만 Go 언어는 그런 구분 없이 모든 객체를 동등하게 취급합니다.

    마치 모든 학생을 똑같이 대하는 공평한 선생님 같다고 할까요?

  2. 비압축적 (Non-compacting): 가비지 컬렉션 과정에서, Go의 GC는 살아있는 객체들을 메모리 이곳저곳으로 옮기지 않습니다.

    객체를 옮기는 데 드는 추가적인 작업 시간과 참조 주소를 업데이트하는 번거로움을 피하기 위한 전략인데요.

    이사 가지 않고 제자리에서 청소하는 방식이라고 생각하면 쉽습니다.

  3. 동시적 (Concurrent): Go의 GC는 프로그램이 열심히 돌아가고 있는 와중에도 동시에 작동합니다.

    즉, 프로그램 실행을 잠시 멈추지 않고도 쓰레기 청소를 할 수 있다는 뜻이죠.

    덕분에 프로그램이 멈칫거리는 시간을 최소화하여 부드러운 실행을 보장합니다.

이러한 설계는 Go 언어가 메모리를 할당하는 방식(tcmalloc 기반)과도 깊은 관련이 있습니다.

Go의 메모리 할당자는 메모리 조각화(fragmentation, 메모리가 잘게 쪼개져서 큰 덩어리를 할당하기 어려워지는 현상)를 효과적으로 관리하기 때문에, 굳이 객체들을 한곳으로 모으는 '압축' 작업의 필요성이 줄어듭니다.

또한, Go 컴파일러는 '탈출 분석(escape analysis)'이라는 똑똑한 기술을 사용해서 변수가 스택(stack)에 할당될 수 있는지 판단하여 메모리 관리를 더욱 최적화합니다.

"표시하고 쓸어버리자!" 마크 앤 스윕 (Mark-and-Sweep) 알고리즘

Go 언어의 가비지 컬렉터는 '마크 앤 스윕(Mark-and-Sweep)'이라는 고전적이면서도 효과적인 알고리즘을 사용합니다.

이 과정은 크게 두 단계로 나눌 수 있습니다.

  1. 표시 단계 (Mark Phase): 먼저, GC는 프로그램의 뿌리가 되는 객체들(예: 전역 변수, 현재 실행 중인 함수의 변수들)부터 시작해서, 이 객체들이 참조하고 있는 모든 객체들을 찾아다니며 "아직 살아있음!"이라는 표시를 합니다.

    마치 중요한 물건에 스티커를 붙여두는 것과 비슷합니다.

  2. 청소 단계 (Sweep Phase): 표시 단계가 끝나면, GC는 전체 메모리(힙 영역)를 쓱 훑어보면서 "살아있음!" 표시가 없는 객체들을 찾아냅니다.

    이 표시 없는 객체들이 바로 더 이상 사용되지 않는 '쓰레기'들이죠.

    GC는 이 쓰레기들이 차지하고 있던 메모리 공간을 회수하여, 나중에 새로운 객체를 위해 사용할 수 있도록 깨끗하게 비워둡니다.

이 마크 앤 스윕 방식은 살아있는 객체들을 옮길 필요 없이, 사용하지 않는 메모리만 효율적으로 회수할 수 있게 해줍니다.

"흰색, 회색, 검은색!" 삼색 추상화 (Three-Color Abstraction) 기법

Go의 가비지 컬렉터가 프로그램 실행과 동시에 작동(동시적 가비지 컬렉션)할 수 있도록 돕는 아주 중요한 기법이 바로 '삼색 추상화(Three-Color Abstraction)'입니다.

이는 객체들을 세 가지 색깔로 구분하여 관리하는 방식인데요.

  • 흰색 (White): 아직 GC가 검사하지 않은 객체들입니다.

    처음에는 모든 객체가 흰색으로 시작하며, 이들은 잠재적인 쓰레기 후보입니다.

  • 회색 (Gray): GC에 의해 발견되었지만, 이 객체가 참조하고 있는 다른 객체들을 아직 완전히 탐색하지 않은 상태의 객체들입니다.

    GC는 이 회색 객체들을 계속해서 처리해야 합니다.

  • 검은색 (Black): GC에 의해 완전히 처리된 객체들입니다.

    이 객체뿐만 아니라, 이 객체가 참조하는 모든 객체들까지도 검사가 끝났다는 의미입니다.

    검은색 객체는 확실히 살아있는 객체입니다.

GC는 처음에 모든 객체를 흰색으로 표시합니다.

그리고 뿌리 객체들을 회색으로 바꾼 후, 회색 객체들을 하나씩 처리하기 시작합니다.

회색 객체를 처리할 때는 그 객체를 검은색으로 바꾸고, 그 객체가 참조하고 있는 모든 흰색 객체들을 회색으로 바꿉니다.

이 과정을 더 이상 회색 객체가 남아있지 않을 때까지 반복합니다.

이 작업이 모두 끝나면, 여전히 흰색으로 남아있는 객체들은 프로그램에서 더 이상 도달할 수 없는, 즉 사용되지 않는 객체로 간주되어 회수됩니다.

"잠깐! 아직 쓰고 있다고요!" 쓰기 장벽 (Write Barriers)의 역할

GC가 열심히 표시 작업을 하는 동안에도 프로그램은 계속 실행되면서 객체들 사이의 참조 관계를 바꿀 수 있습니다.

만약 이때 잘못 처리하면, 아직 사용 중인 객체를 쓰레기로 착각하고 치워버리는 끔찍한 상황이 발생할 수 있겠죠?

이러한 문제를 막기 위해 Go의 GC는 '쓰기 장벽(Write Barrier)'이라는 메커니즘을 사용합니다.

쓰기 장벽은 프로그램 코드에서 포인터(객체의 메모리 주소를 가리키는 변수)의 값이 변경되기 직전이나 직후에 실행되는 아주 작은 코드 조각입니다.

이 코드 조각의 역할은, 표시 단계 동안 객체 참조 관계가 바뀌더라도 삼색 추상화의 규칙이 깨지지 않도록 보장하여, 살아있는 객체가 실수로 회수되는 것을 방지하는 것입니다.

마치 중요한 작업 중에 실수가 없도록 감시하는 역할을 한다고 볼 수 있습니다.

Go 언어 가비지 컬렉션 방식의 장점들!

Go 언어의 가비지 컬렉터 설계는 여러 가지 멋진 장점들을 제공합니다.

  • 짧아진 멈춤 시간 (Reduced Pause Times): 프로그램과 동시에 작동하기 때문에, GC로 인해 프로그램 실행이 멈추는 'Stop-the-World (STW)' 현상을 최소화합니다.

    덕분에 프로그램이 더욱 부드럽게 실행될 수 있습니다.

  • 간편해진 메모리 관리 (Simplified Memory Management): 개발자들이 직접 메모리를 할당하고 해제하는 복잡한 작업을 신경 쓰지 않아도 됩니다.

    이는 메모리 누수(memory leak)와 같은 버그 발생 가능성을 크게 줄여줍니다.

  • 효율적인 메모리 활용 (Efficient Memory Utilization): 객체를 옮기지 않는 비압축 방식과 Go의 효율적인 메모리 할당자가 결합되어, 심각한 메모리 조각화 없이 메모리를 효율적으로 사용할 수 있도록 합니다.

마무리하며: Go 언어의 든든한 메모리 지킴이, 가비지 컬렉터!

지금까지 Go 언어의 가비지 컬렉터가 어떻게 메모리를 효율적으로 관리하면서도 프로그램 실행에 미치는 영향을 최소화하는지 자세히 알아봤습니다.

동시적이고, 비세대적이며, 비압축적인 설계 원칙, 그리고 마크 앤 스윕 알고리즘과 삼색 추상화 기법의 조화는 Go 언어 애플리케이션이 부드럽게 실행될 수 있도록 돕는 핵심 기술입니다.

덕분에 Go 개발자들은 복잡한 메모리 관리에 대한 부담을 덜고, 더욱 창의적이고 생산적인 코드 작성에 집중할 수 있게 되는 것이죠!

Go 언어의 숨은 조력자, 가비지 컬렉터의 활약을 기억해주세요!