Go 언어의 메모리 관리 개요

An overview of memory management in Go (2021)

작성자
HackerNews
발행일
2021년 05월 09일

핵심 요약

  • 1 Go는 스택 할당을 선호하며, 가비지 컬렉터는 힙 메모리 관리에 중점을 둔다.
  • 2 Go의 가비지 컬렉터는 비세대(non-generational), 동시(concurrent), 삼색(tri-color) 마크 앤 스윕(mark and sweep) 알고리즘을 사용한다.
  • 3 수동 메모리 관리의 문제점을 해결하기 위해 Go는 자동 가비지 컬렉션을 채택하여 개발자가 비즈니스 로직에 집중할 수 있도록 돕는다.

도입

프로그램 실행 중 생성되는 객체는 메모리에 저장되며, 더 이상 필요 없을 때 효율적으로 제거되어야 합니다. 이 과정을 메모리 관리라고 하며, Go 언어는 가비지 컬렉터(Garbage Collector)를 통해 이를 자동화합니다. 본 글은 메모리 관리의 기본 개념을 소개하고, 특히 Go 1.16 버전 이후의 Go 언어에서 가비지 컬렉션이 어떻게 구현되고 작동하는지 상세히 설명합니다. 수동 메모리 관리의 한계와 자동 메모리 관리의 이점을 비교하며 Go의 메모리 관리 전략을 조명합니다.

메모리 관리는 프로그램의 성능과 안정성에 직접적인 영향을 미칩니다. 수동 메모리 관리 방식과 자동 메모리 관리 방식은 각기 다른 장단점을 가집니다.Go는 자동 메모리 관리를 채택하여 개발 편의성을 높였습니다.

수동 메모리 관리의 한계

C와 같은 언어에서는 프로그래머가 malloc이나 calloc으로 메모리를 할당하고, free 함수를 호출하여 명시적으로 해제해야 합니다. 이는 메모리에 대한 높은 제어권을 제공하지만, 다음과 같은 두 가지 주요 프로그래밍 오류를 유발할 수 있습니다.

  • 댕글링 포인터(Dangling Pointer): 메모리가 너무 일찍 해제되어 포인터가 유효하지 않은 메모리 위치를 가리키는 경우 발생합니다. 이는 예측 불가능한 프로그램 동작으로 이어집니다.

  • 메모리 누수(Memory Leak): 사용하지 않는 객체의 메모리를 해제하지 않아 메모리가 점차 고갈되는 현상입니다. 이는 프로그램 속도 저하 또는 충돌을 야기할 수 있습니다.

자동 메모리 관리 (가비지 컬렉션)

Go는 이러한 수동 관리의 위험을 피하기 위해 가비지 컬렉션을 통한 자동 동적 메모리 관리를 제공합니다. 가비지 컬렉션은 보안 강화, 운영체제 간 이식성 향상, 코드량 감소, 런타임 검증 등의 이점을 제공하며, 개발자가 메모리 관리 걱정 없이 핵심 비즈니스 로직에 집중할 수 있도록 돕습니다.

스택(Stack)과 힙(Heap)

프로그램은 객체를 스택과 힙이라는 두 가지 메모리 위치에 저장합니다. 가비지 컬렉션은 주로 힙 메모리를 대상으로 작동합니다.

  • 스택: LIFO(Last-In, First-Out) 구조로, 함수 호출 시 생성되는 스택 프레임과 지역 변수를 저장합니다. Go는 고루틴(goroutine)당 스택을 가지며, 컴파일러의 이스케이프 분석(escape analysis)을 통해 생명주기가 명확한 변수는 스택에 할당됩니다.

  • 힙: 함수 외부에서 참조되거나 생명주기가 불분명한 객체(예: Go 구조체)를 저장합니다. 힙에 할당된 객체는 포인터로 참조되며, 프로그램 실행 중 계속해서 증가할 수 있습니다.

Go의 가비지 컬렉터 구현

Go의 가비지 컬렉터는 “비세대(non-generational) 동시(concurrent) 삼색(tri-color) 마크 앤 스윕(mark and sweep)” 방식으로 작동합니다.

  • 비세대(Non-generational): Go 컴파일러는 이스케이프 분석을 통해 단명하는 객체를 스택에 할당하므로, 힙에는 장명 객체가 많아 세대별 GC의 효율이 낮아 비세대 방식을 채택합니다.

  • 동시(Concurrent): 가비지 컬렉터가 애플리케이션 코드(뮤테이터 스레드)와 동시에 실행되어 프로그램 중단 시간(Stop the World)을 최소화합니다.

  • 마크 앤 스윕(Mark and Sweep): 두 단계로 구성됩니다.

    • 마크(Mark) 단계: 컬렉터가 힙을 탐색하여 사용 중인 객체(라이브 객체)를 표시합니다. Go는 삼색 알고리즘을 사용합니다.
      • Stop the World: GC 시작 시 모든 고루틴을 일시 중지하고 쓰기 장벽(write barrier)을 활성화하여 데이터 무결성을 보장합니다.
      • 삼색 알고리즘: 모든 객체를 흰색(white)으로 초기화하고, 루트 객체(스택, 전역 변수, 힙 포인터)를 회색(grey)으로 표시합니다. 워커는 회색 객체를 스캔하여 참조하는 객체를 회색으로 만들고 자신은 검은색(black)으로 변경합니다. 검은색은 해당 객체가 사용 중이며 모든 참조가 스캔되었음을 의미합니다.
    • 스윕(Sweep) 단계: 마크 단계가 완료된 후, 다시 Stop the World를 수행하고 흰색으로 남아있는(더 이상 참조되지 않는) 객체들의 메모리를 해제합니다.

GC 트리거

Go의 가비지 컬렉션은 현재 사용 중인 메모리에 비례하여 추가 메모리가 할당될 때 다시 시작됩니다. GOGC 환경 변수(기본값 100)를 통해 이 비율을 조절할 수 있으며, 이는 GC 비용이 할당 비용에 비례하도록 유지합니다.

결론

Go 언어의 가비지 컬렉터는 메모리 관리를 런타임에 추상화하여 개발자가 핵심 로직에 집중할 수 있게 하며, Go의 뛰어난 성능에 중요한 역할을 합니다. 비세대, 동시, 삼색 마크 앤 스윕 방식을 통해 힙 메모리를 효율적으로 관리하며, 컴파일러의 이스케이프 분석을 통한 스택 할당 최적화는 가비지 컬렉션의 오버헤드를 최소화합니다. 이러한 설계는 Go 프로그램의 안정성과 효율성을 높이는 데 기여하며, 개발자는 `GOGC`와 같은 내장 도구를 활용하여 가비지 컬렉션 동작을 미세 조정할 수 있습니다. Go의 메모리 관리 전략은 복잡한 시스템에서도 예측 가능하고 안정적인 성능을 제공하는 데 필수적입니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!