메모리와 GC(가비지 컬렉터)

메모리

💻 프로그램 실행 메모리 영역 (4가지 주요 영역)

프로그램이 실행될 때 운영체제는 해당 프로그램(프로세스)을 위해 가상 메모리 공간을 할당합니다. 이 공간은 일반적으로 네 가지 주요 영역으로 나뉩니다.

1. 코드 영역 (Code Segment, Text Segment)

  • 저장 내용: 프로그램의 실행 가능한 명령어들, 즉 기계어 코드가 저장됩니다.
  • 특징:
    • 읽기 전용 (Read-Only): 프로그램 코드가 실행 중 의도치 않게 변경되는 것을 방지하기 위해 이 영역은 읽기 전용으로 설정됩니다.
    • 공유 가능: 동일한 프로그램이 여러 개 실행될 경우, 코드는 변경되지 않으므로 이 영역을 공유하여 메모리를 절약할 수 있습니다.
    • 생명 주기: 프로그램이 시작될 때 메모리에 로드되어, 종료될 때 해제됩니다.

2. 데이터 영역 (Data Segment)

  • 저장 내용: 프로그램이 사용하는 정적 변수 (Static variables), 전역 변수 (Global variables), 그리고 초기화된 데이터가 저장됩니다.
  • 특징:
    • 생명 주기: 프로그램 시작 시 할당되어, 종료 시 해제되므로 프로그램 전체 수명을 가집니다.
    • 공유 가능: 모든 스레드에서 접근 및 공유가 가능합니다. 특히 C#에서는 static 키워드로 선언된 변수가 이 영역에 저장됩니다.
    • 데이터 종류: 초기화된 데이터와 초기화되지 않은 데이터 영역(BSS)으로 더 세분화될 수 있습니다.

3. 스택 영역 (Stack)

  • 저장 내용: 주로 함수 호출과 관련된 정보 및 값 타입(Value Type) 데이터를 저장합니다.
    • 스택 프레임 (Stack Frame, Activation Record): 함수가 호출될 때마다 생성되어, 지역 변수 (Local variables), 매개 변수 (Parameters), 그리고 함수가 실행을 마친 후 돌아갈 반환 주소 (Return Address) 등을 저장합니다.
  • 특징:
    • LIFO (Last-In, First-Out) 구조: 가장 나중에 들어온 것이 가장 먼저 나가는 구조로, 함수 호출 및 복귀 순서와 일치합니다.
    • 자동 할당/해제: 컴파일 시 크기가 결정되며, 함수 호출 시 자동으로 할당되고 함수 종료 시 자동으로 해제됩니다. 이 과정이 매우 빠르고 효율적입니다.
    • C# 관점: C#의 값 타입 (예: int, char, struct 등) 변수와 **참조 타입 변수의 주소 (참조)**가 이 영역에 저장됩니다.

4. 힙 영역 (Heap)

  • 저장 내용: 동적으로 할당되는 객체들이 저장됩니다.
    • C#에서는 new 키워드를 통해 생성된 객체, 클래스 인스턴스, 배열, 문자열 등 대부분의 **참조 타입 (Reference Type)**의 실제 데이터가 이 영역에 저장됩니다.
  • 특징:
    • 동적 할당: 프로그램 실행 중 (런타임)에 크기가 결정되고 할당됩니다.
    • 수명: 참조하는 변수(스택 또는 데이터 영역에 있음)가 살아있는 동안 유지됩니다.
    • 속도: 스택에 비해 할당 및 접근 비용이 높아 상대적으로 느립니다.
    • 관리: **가비지 컬렉터 (GC)**가 자동으로 관리하며, 더 이상 참조되지 않는 객체는 GC에 의해 수거되어 메모리가 해제됩니다.

♻️ 가비지 컬렉터 (Garbage Collector, GC)와 동작 원리

C#의 .NET 환경에서 GC는 힙 영역의 메모리를 자동으로 관리하여 개발자가 수동으로 메모리를 해제할 필요가 없게 만들고 **메모리 누수 (Memory Leak)**를 방지하는 핵심적인 요소입니다.

GC의 주요 목표

  1. 메모리 해제 자동화: 더 이상 사용되지 않는 객체를 식별하고 자동으로 메모리를 회수합니다.
  2. 단편화 제거 (Compaction): 메모리 회수 후 힙 내부에 발생하는 작은 빈 공간(단편화)을 제거하여 연속적인 큰 빈 공간을 확보합니다.

1. 객체 참조 추적 (Root Set 탐색)

GC는 어떤 객체를 수거해야 할지 결정하기 위해 **루트 (Root)**에서부터 시작하여 객체들의 참조 관계를 추적합니다.

  • 루트 객체: GC가 살아있는 객체라고 가정하고 추적을 시작하는 객체들입니다.
    • 스택: 현재 실행 중인 함수의 지역 변수매개 변수에 저장된 객체 참조.
    • 데이터 영역: static 변수에 저장된 객체 참조.
    • 레지스터: CPU 레지스터에 임시 저장된 객체 참조.
  • 추적 원리: 루트에서부터 접근 가능한 (직접 또는 간접적으로 참조되는) 모든 객체는 **'살아있는 객체' (Live Object)**로 간주하여 수거 대상에서 제외됩니다. 참조가 없는 객체만이 수거 대상이 됩니다.

2. 세대 관리 (Generational Collection)

대부분의 프로그램에서 객체는 짧은 수명을 갖는 경향이 있습니다. GC는 이 특성을 활용하여 힙을 **세대 (Generation)**로 나누어 효율성을 높입니다.

 

세대 설명 GC 빈도
Gen 0 새로 생성된 객체가 위치합니다. 가장 짧은 수명을 가지며, GC는 주로 이 세대를 빠르게 검사하여 회수합니다. 가장 빈번
Gen 1 Gen 0에서 GC 사이클을 한 번 살아남은 객체들이 이동합니다. 중간 정도의 수명을 가집니다. Gen 0보다 적게
Gen 2 Gen 1에서 GC 사이클을 또 살아남은 객체들, 즉 장기 생존 객체들이 위치합니다. 가장 적게

목적: 짧은 수명의 객체는 빠르게 회수하고, 오래 살아남을 가능성이 높은 객체 (Gen 2)는 덜 자주 검사하여 GC의 성능 부하를 줄입니다.

3. GC 과정 (3단계)

  1. 마킹 (Marking):
    • GC가 작동하면, 모든 실행 중인 스레드를 일시 중지하고 (Stop-the-World) 루트에서부터 참조를 추적하여 살아있는 객체들을 표시 (마킹)합니다.
  2. 스윕 (Sweeping):
    • 마킹되지 않은 객체들, 즉 더 이상 참조되지 않는 쓰레기 (Garbage) 객체들을 식별하고 메모리를 해제합니다.
  3. 압축 (Compaction):
    • 메모리가 해제된 후 힙 중간중간에 생기는 단편화를 제거하기 위해, 살아있는 객체들을 힙의 한쪽 끝으로 연속적으로 당겨 배치합니다.
    • 결과: 힙의 나머지 부분에 하나의 크고 연속적인 빈 공간이 만들어져 다음 객체 할당 시 더 효율적으로 메모리를 사용할 수 있게 됩니다.

'C# > 객체지향 활용과 응용 개발' 카테고리의 다른 글

추상 클래스  (0) 2025.10.10
상속  (0) 2025.10.10
Static  (0) 2025.10.10
생성자와 클래스 심화  (0) 2025.10.05
객체지향과 클래스  (0) 2025.10.05