Memory 구조
- 프로그램 실행 시 메모리는 4개의 주요 영역으로 나뉨
- 코드 (Code Segment) : 실행할 프로그램 코드 저장
- 데이터 (Data Segment) : static 변수 저장
- 스택 (Stack) : 함수 호출 시 생성되는 프레임, 지역변수 / 매개변수 / 반환 주소 저장
- 힙 (Heap) : 동적으로 할당된 객체 저장

C# 관점
- 스택 : 함수 호출과 로컬 변수 관리
- 힙 : 동적 객체 저장, 참조형 타입 대부분 저장
Stack
함수 호출 시 생성되는 메모리 프레임 관리
- 저장 내용
- 매개변수
- 지역변수
- 반환주소
- 특징
- LIFO 구조 (Last In First Out) 후입 선출
- 자동 할당/해제 → 빠름
- 함수 종료 시 프레임 제거
void Foo()
{
int x = 10; // x는 스택에 저장
iny y = 20;
}


class Person
{
public string Name;
}
class Program
{
void Bar()
{
Person p = new Person(); // p는 스택에 참조(주소) 저장
p.Name = "Kim"; // 실제 객체는 힙에 저장
}
void PrintSum(int x, int y)
{
int sum = x + y; // sum도 스택에 저장
Console.WriteLine(sum);
}
struct Point
{
public int X, Y;
}
void Draw()
{
Point pt = new Point { X = 10, Y = 20 }; // pt 구조체 전체가 스택에 저장
}
}
Heap
동적 메모리 저장 공간
- 저장 대상
- new 키워드로 생성된 객체
- 클래스 인스턴스, 배열, 문자열 등 참조형 타입 대부분
- 특징
- 동적 할당 → 런타임에 크기 결정 가능
- 수명 → 참조가 살아 있는 동안 유지
- 속도 → 스택보다 느림 (할당 접근 비용이 더 높음)
- 관리 → GC가 필요할 때 수거
class Person
{
public string Name;
}
class Program
{
void Foo()
{
Person p = new Person(); // p는 스택 참조, Person 객체는 힙에 저장
p.Name = "Kim";
}
}

class Person
{
public string Name;
public int Age;
}
class ExampleClass
{
public Person Friend; // 참조형 필드
public int[] Scores; // 배열도 힙에 저장
}
class Program
{
void HeapExamples()
{
Person p1 = new Person();
p1.Name = "Kim";
p1.Age = 25;
Person p2 = new Person (Name = "Lee", Age = 30);
// 참조 복사 (참조만 스택에, 객체는 힙)
Person p3 = p2;
// 클래스 내부에 참조형 필드
ExampleClass example = new ExampleClass();
example.Friend = new Person { Name = "Park", Age = 28 );
example.Scroes = new int[] { 90, 85, 100 } // 배열도 힙에 저장
ExampleClass example2 = example; // example2 참조는 스택, 객체는 힙
}
}
Data Segment
readonly, static 변수 등 저장
프로그램 시작 시 생성, 종료 시 해제
모든 스레드에서 공유 가능
class Example
{
public static int Count = 0;
public static string Name;
}
Code Segment
프로그램의 실행 가능한 기계어 코드 저장, 실행해야할 명령어만 저장
읽기 전용 → 코드가 의도치 않게 변경되는 것을 방지
프로그램 시작 시 메모리에 로드, 종료시 해제
모든 스레드에서 공유 가능
class Program
{
static void Main()
{
Foo(); // 코드 영역
}
void Foo()
{
Console.WriteLine("Hello, World!"); // 코드 영역에 위치
}
}
GC - Garbage Collector
힙에 있는 더이상 참조되지 않는 객체를 자동으로 수거하여 메모리 해제
목적 : 메모리 누수 방지, 자동 메모리 관리
- 대상
- 힙에 있는 참조형 객체
- 더 이상 스택이나 다른 객체에서 참조되지 않는 객체
class Person
{
public string Name;
}
class Program
{
void Foo()
{
Person p = new Person(); // 힙에 객체 생성
} // p 참조 사라짐 -> GC 대상
}
- 동작 원리
- 객체 참조 추적
- 스택 데이터 영역, 다른 객체에서 참조되는 객체는 살아있음
- 참조가 없는 객체는 수거 대상
- 세대 관리
- Gen 0 : 새로 생성된 객체
- Gen 1 : Gen 0 에서 살아남은 객체
- Gen 2 : 오래 살아남은 객체
- 목적 : 짧은 수명 객체를 빠르게 수거, 오래된 객체는 덜 검사
- GC 과정
- 마킹 (Mark) : 살아있는 객체 표시
- 스윕 (Sweep) : 참조 없는 객체 제거
- 압축 (Compact) : 단편화 제거, 힙 연속성 유지
- 객체 참조 추적
스윕 전에 마킹을 수행하는 이유 예시
class Node
{
public string Name;
public Node Next;
}
class Program
{
void Example()
{
// B 객체 생성
Node b = new Node { Name = "B" };
// A 객체 생성, B 객체를 Next로 참조
Node a = new Node { Name = "A", Next = b };
// 외부에서 a만 참조하고 b는 외부 참조 없음
Node root = a;
// 이제 a만 남음, b는 a 내부에서만 참조
// GC 시작
}
}
객체 참조 추적 (root Set 탐색)
- 목적 : 어떤 객체가 살아있는지 확인
- 루트(Root) 객체 확인
- 스택 : 지역 변수, 매개변수
- 데이터 영역 : static 변수, 전역 변수
- 레지스터 : CPU에 임시 저장된 참조
- 루트에서 접근 가능한 모든 객체는 살아있는 객체로 표시
static Person p; // 데이터 영역
void Foo()
{
Person local = new Person(); // 스택 참조
}
p와 local에서 참조되는 객체는 살아있음
스택 프레임 종료 후 local은 더 이상 루트에 없으므로 수거 대상
세대(Generation) 관리
- 목적 : 효율적인 수거
- 방식 : GC 수거에서 살아남은 횟수를 기반으로 세대 결정
- 효과 : 짧은 수명 객체를 자주, 오래된 객체는 덜 검사 → 성능 최적화
| 세대 | 대상 | 특징 |
| Gen 0 | 새로 생성된 객체 | 대부분 짧은 수명 → 자주 수거 |
| Gen 1 | Gen 0 에서 살아남은 객체 | 중간 수명, 적당히 수거 |
| Gen 2 | 오래 살아남은 객체 | 장기 수명, 수거 빈도 낮음 |
GC 과정 (Mark-Sweep-Compact)
- 마킹 (Mark)
- 루트에서 도달 가능한 객체 모두 표시
- 살아있는 객체 = Marked
- 루트에서 접근 불가하면 Garbage 후보
- 스윕 (sweep)
- 힙 전체를 순회하며
- Marked → 유지
- Mark되지 않은 → 삭제, 메모리 해제
- 단순 Mark-Sweep은 메모리 단편화 발생 가능
- 힙 전체를 순회하며
- 암축 (Compact)
- 삭제 후 남은 객체를 연속된 공간으로 이동
- 목적 : 단편화 제거, 힙 연속성 유지
- 스택 / 힙 접근시 캐시 효율 증가
GC 시점
- 자동 : 힙 사용량, 메모리 부족, 주기적 검사
- 수동 호출 : Gc.Collect() → 즉시 수거 (권장 안 함, 성능 영향)
class MyClass
{
public int Id;
public MyClass(int id) { Id = id; }
~MyClass()
{
Console.WriteLine($"MyClass {Id} 소멸자 호출!");
}
}
static void Main()
{
Console.WriteLine("프로그램 시작");
//객체 생성 (힙에 할당)
MyClass obj = new MyClass(1);
// WeakReference 생성 (객체 생존 여부 확인용)
WeekReference weakRef = new WeakReference(obj);
Console.WriteLine("객체 생성 후 alive? " + weakRef.IsAlive); // True
// 아직 참조가 살아있는 상태에서 GC 시도
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("GC 수행 후 alive? " + weakRef.IsAlive);// True
// 참조 제거
obj = null;
// obj 자체는 null이지만, 객체가 아직 살아있는지 확인
Console.WriteLine("obj 참조 제거 직후 obj != null? " + (obj != null)); // False
Console.WriteLine("obj 참조 제거 직후 WeakRef.IsAlive? " + weakRef.IsAlive); // True
// GC 수행
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("GC 수행 후 alive? " + weakRef.IsAlive); // False
Console.WriteLine("프로그램 종료");
}

'C#' 카테고리의 다른 글
| C# - LockConsoleSize() : 콘솔 창 크기 고정 및 리사이즈 방지 (1) | 2025.10.14 |
|---|---|
| C# - 콘솔 폰트 크기 변경하기 SetCurrentConsoleFontEx (1) | 2025.10.13 |
| C# - 정렬 알고리즘 (Sorting Algoritm) : 버블/선택/삽입/병합/퀵 정렬 (0) | 2025.10.13 |
| C# - 피셔 예이츠 셔플 (Fisher Yates Shuffle) (0) | 2025.10.13 |
| C# - 가중치 그래프 - 다익스트라 알고리즘 으로 지하철 노선 최단경로 구하기 (0) | 2025.10.13 |