IT Study/SW 개발 및 프로그래밍

💻 가비지 컬렉션(Garbage Collection) 방식과 메모리 누수 추적

cs_bot 2025. 4. 15. 01:10

1. 가비지 컬렉션(GC)의 개요

  • 프로그래밍 언어가 사용하는 힙 영역의 동적 메모리를 자동으로 관리하는 메커니즘
  • 사용되지 않는 객체를 탐지하고 회수하여 메모리 부족 및 누수를 방지함
  • 수동 메모리 관리 방식(C/C++ 등)과 대비되는 자동화된 메모리 관리 구조임

2. 가비지 컬렉션의 주요 방식 분류

1) 참조 계수 방식(Reference Counting)

  • 객체마다 참조 수를 저장하고, 참조가 0이 되면 해당 객체 회수
  • 즉각적인 회수 가능성이 장점이나, 순환 참조 문제로 메모리 누수 발생 가능성 존재
  • 대표 언어: Objective-C, Swift, Python 일부

2) 마크 앤 스위프(Mark and Sweep)

  • 루트(root) 객체에서 시작하여 참조된 객체를 마킹(mark)하고, 마킹되지 않은 객체를 회수(sweep)하는 방식
  • 순환 참조 문제 해결 가능
  • GC 중단 시간(long pause)이 단점으로 지적됨

3) 카피(Copying) 방식

  • 힙 공간을 두 영역(from, to)으로 나누고, 사용 중인 객체를 to 영역으로 복사 후 from 영역 전체 제거
  • 메모리 단편화 방지에 유리하나, 전체 복사에 의한 오버헤드 존재

4) 마크 앤 컴팩트(Mark and Compact)

  • 마킹 후, 유효 객체를 한쪽으로 압축(compact)하여 단편화 방지
  • 객체 복사로 인한 GC 시간 증가 단점 존재

5) 세대별 가비지 컬렉션(Generational GC)

  • 객체 생존 주기 분석을 기반으로 메모리 영역을 young/old generation으로 구분하고, young 영역을 집중적으로 수거
  • 대부분의 객체가 금방 죽는다는 “객체 사망률의 시간 지역성” 가정 기반
  • 대표 언어: Java, C# (JVM/CLR)

6) 실시간/병행/병렬 GC 방식

  • Stop-the-world을 최소화하여 GC 도중에도 애플리케이션이 실행 가능하게 설계
  • 예: G1GC (Java), ZGC, Shenandoah, .NET Server GC 등

3. 언어별 가비지 컬렉션 구현 예시

언어 GC 지원 방식 세부 특징
Java Generational GC, G1GC 등 객체 힙 분할, 다양한 GC 정책 선택 가능
Python 참조 계수 + 순환 참조 탐지 gc 모듈로 수동 수거 제어 가능
JavaScript Mark & Sweep 기반 GC V8 엔진의 Incremental + Generational 방식 적용
Go 병렬 GC STW 시간 최소화, GC 지연 감소 지향
C# (.NET) Generational + Server GC Large Object Heap(LOH) 분리 관리

4. 메모리 누수(Memory Leak) 발생 원인과 사례

1) 참조 해제 누락

  • 사용 후 참조가 남아 GC가 객체를 수거하지 못함
  • 예: 이벤트 리스너 등록 후 제거 누락, 글로벌 변수에 객체 보관

2) 캐시/컬렉션 누수

  • Map, List, Cache 등에 객체가 누적되며 제거되지 않아 메모리 지속 점유
  • WeakReference 등을 사용하여 누수 방지 가능

3) 순환 참조

  • 참조 계수 방식에서 순환 구조로 인해 참조 수가 0이 되지 않아 수거 불가
  • 예: A → B, B → A 참조 구조

4) 리소스 미해제

  • 파일, 소켓, DB 연결 등 OS 자원을 수거하지 않아 발생
  • try-finally, RAII 패턴 등으로 자원 명시적 해제 필요

5. 메모리 누수 추적 기법

1) 코드 정적 분석

  • 컴파일 타임에 메모리 할당/해제 흐름을 분석하여 누수 가능성 확인
  • 도구: FindBugs(Java), Clang Static Analyzer(C/C++), SonarQube

2) 동적 메모리 분석

  • 실행 중 힙 덤프를 분석하여 객체 생존 시간, 참조 체인 확인
  • 도구:
    • Java: VisualVM, Eclipse MAT, JProfiler
    • C/C++: Valgrind, AddressSanitizer
    • Python: objgraph, tracemalloc

3) Runtime Hooking 및 커스텀 Allocator

  • 메모리 할당 및 해제 지점을 가로채어 추적 로그 작성
  • 고급 C/C++ 디버깅 환경에서 자주 사용됨

4) Leak Detection Library 활용

  • 언어별 전용 라이브러리 기반 자동 추적 수행
  • 예: LeakCanary(Android), Memory Profiler(iOS)

6. 메모리 누수 방지를 위한 설계/코딩 원칙

  • 객체 수명 주기 명확화 및 주기적 참조 제거
  • 이벤트 핸들러/리스너 해제 패턴 준수
  • GC에 의존하지 않는 명시적 해제 습관
  • 약한 참조(WeakReference) 또는 LRU 캐시 전략 사용
  • 자원 해제를 위한 try-with-resources, using, RAII 등 패턴 활용

7. 결론

  • 현대 소프트웨어 시스템에서 안정적인 메모리 관리는 성능과 안정성의 핵심 요소로 작용함
  • GC는 자동화된 메모리 관리를 제공하지만, 개발자의 메모리 이해 부족은 GC 한계를 초래할 수 있음
  • 메모리 누수는 성능 저하, OOM(Out Of Memory) 등 심각한 장애로 이어질 수 있으므로, 사전 예방과 사후 추적 기법의 병행 필요함
  • 정적 분석, 동적 추적, 모니터링 도구를 종합적으로 활용하는 다층적 메모리 관리 전략이 요구됨