✅ GC의 목적
- 힙(Heap)에 쌓인 더 이상 참조되지 않는 객체를 자동으로 찾아 제거하여 메모리 누수를 방지
- 프로그래머가 수동으로 delete 같은 메모리 해제를 하지 않아도 되게 함 (C++과 비교되는 장점)
🔍 GC의 동작 흐름 요약
1. 객체가 생성되면 힙(Heap)에 저장됨
var player = new Player(); // new → 힙 메모리 할당
- 이 객체는 스택 변수 player가 참조하고 있음
- 참조가 사라지지 않는 한, GC는 이 객체를 제거하지 않음
2. GC는 "참조 그래프"를 분석해서 도달 불가능한 객체를 수거
- 루트(스택 변수, static, CPU 레지스터 등)에서 시작해,
- 따라갈 수 없는 객체는 "고아 객체(unreachable)" → 수거 대상
3. 수거 시점: GC는 항상 즉시 작동하지 않음
- 다음 조건 중 일부일 때 작동:
- 힙 메모리가 부족해졌을 때
- 명시적으로 GC.Collect() 호출했을 때
- 백그라운드 GC가 자동으로 주기적으로 실행
➡ 그래서 GC는 "비결정적(언제 실행될지 보장되지 않음)"
✅ 세대(Generation) 기반 GC
GC는 효율을 위해 객체를 '세대(Generation)'별로 분류합니다:
세대의미특징
| Gen 0 | 가장 최근에 생성된 객체 | 대부분 한두 프레임만 존재하는 임시 객체 |
| Gen 1 | Gen 0에서 살아남은 객체 | 아직도 참조됨 |
| Gen 2 | 오래 살아남은 객체 | 게임 매니저, 싱글톤 등 |
GC는 먼저 Gen 0부터 수거하고, 살아남은 객체는 다음 세대로 승급됩니다.
➡ 오래된 객체는 GC 비용이 높아서 자주 검사하지 않음
➡ 게임 루프 안에서 Gen 0 대상의 임시 객체를 자주 만들면 GC 부담 커짐!
✅ 참조 그래프란?
프로그램 실행 중 루트(Root)부터 시작해서
"누가 누구를 참조하고 있는가"를 따라가 만든 연결 구조
🔹 루트(Root)에는 이런 것들이 포함됩니다:
| 스택 변수 | 메서드 내부의 지역 변수 |
| static 변수 | GameManager.instance, 전역 설정 등 |
| 레지스터에 올라간 변수 | 현재 실행 중인 코드의 변수 |
| GCHandle로 고정한 객체 | GCHandle.Alloc(obj) 등 |
🔍 GC의 참조 그래프 분석 과정
- GC가 시작되면, 우선 루트 객체들을 스캔합니다
(예: player, GameManager, SceneManager 등) - 루트에서 참조하고 있는 객체들을 따라갑니다
- 그 객체들이 다시 참조하는 객체들도 따라갑니다 → 트리 구조 생성
- 이 참조 경로에서 단 한 번도 도달하지 못한 객체는
→ “더 이상 필요 없는 객체” → 수거 대상(Garbage)
✅ 순환 참조도 상관없다!
class Node
{
public Node next;
}
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a; // 순환참조!
- GC는 순환 참조 자체를 문제로 보지 않음
- 중요한 건 "루트에서 접근 가능한가"
- 루트에서 b에 접근 가능하면 → b는 물론 b가 참조하는 a도 함께 살아남음
- 루트에서 둘 다 접근 불가능하면 → 순환 참조라도 둘 다 수거됨
✅ IDisposable과 using 문의 역할
이제 IDisposable과 using은 GC의 수거와는 별도지만, 매우 중요한 역할을 해요.
🔹 어떤 객체는 GC가 수거해도 리소스를 자동으로 해제하지 않음
예:
- 파일 핸들 (FileStream)
- DB 연결 (SqlConnection)
- Unity의 Texture2D, AudioClip (비관리 리소스)
- 네트워크 소켓 등
➡ 이런 객체는 명시적으로 Dispose()를 호출해서 정리해야 함
🔧 방법 1: using 문 사용
using (var stream = new FileStream("path.txt", FileMode.Open))
{
// 파일 사용
}
// 여기서 Dispose() 자동 호출됨
- using은 블록이 끝나면 Dispose()를 자동 호출함
- 예외가 발생해도 안전하게 정리됨 (try-finally 자동 포함됨)
🔧 방법 2: IDisposable 직접 구현
public class MyClass : IDisposable
{
public void Dispose()
{
// 리소스 해제 로직
}
}
→ using 구문에서 사용 가능해짐
🎯 GC는 "메모리"는 수거해도, "자원(리소스)"은 수거하지 않아요.
그래서 참조를 끊어야 GC가 작동하고,
자원은 Dispose()로 직접 정리해야 하는 것입니다.
'C#' 카테고리의 다른 글
| [C#] 구조체를 쓰는 이유 (1) | 2025.08.10 |
|---|---|
| [C#] Boxing과 UnBoxing (3) | 2025.07.25 |
| [C#] LINQ와 Lazy Evaluation(지연 평가) (2) | 2025.07.25 |
| [C#] IEnumerable을 쓰는 이유 (2) | 2025.07.24 |
| [C#] delegate vs Action (1) | 2025.06.16 |