원리: 객체가 참조될 때 참조 카운트를 증가시키고, 참조가 해제되면 카운트를 감소. 카운트가 0이 되면 메모리 해제.
장점:
참조 카운트만 보고 즉각적으로 객체의 생명 주기 관리 가능.
멀티스레드 환경에서는 특정 객체의 수명을 독립적으로 관리하기 쉬움.
단점:
순환 참조 문제: 서로 참조하는 객체가 GC의 대상이 되지 않음.
추적 비용: 참조가 변할 때마다 즉각적으로 카운트를 업데이트해야 하므로 성능에 부담.
멀티스레드 환경의 동기화 문제: 참조 카운트 변경 시 락(lock)을 사용해야 할 수 있어 성능 저하 가능.
원리: 루트(Root) 객체에서 시작해 도달 가능한 모든 객체를 추적하고, 도달할 수 없는 객체를 가비지로 판단.
장점:
순환 참조 해결: 그래프 탐색을 통해 모든 참조 관계를 분석하므로 순환 참조 문제가 발생하지 않음.
효율성: 한 번의 주기적 검사로 많은 객체를 동시에 정리할 수 있음.
메모리 최적화: 객체를 힙에서 재배치(compaction)해 메모리 조각화를 방지.
단점:
스톱 더 월드(Stop-the-World): 모든 애플리케이션 스레드를 멈추고 수행해야 할 때 응답성이 낮아질 수 있음(현대 GC는 이를 완화하는 기술 도입).
추적 오버헤드: 주기적 검사가 필요하며, 대규모 애플리케이션에서는 시간이 오래 걸릴 수 있음.
비교 항목 레퍼런스 카운팅 트레이싱 방식
순환 참조 문제 해결 불가 해결 가능
실시간성 객체 참조 변경 시 즉각 처리 가능 주기적으로만 처리
성능 비용 참조 변화 이벤트가 많을수록 비용 증가 주기적 검사로 성능을 효율적으로 조절 가능
멀티스레드 안정성 락이나 CAS 같은 동기화가 필요할 수 있음 힙 상태를 전체적으로 검사하므로 일관성 보장
메모리 조각화 방지 불가 객체 재배치를 통해 조각화를 방지
객체 참조의 복잡성: 객체 간의 참조 관계는 런타임 동안 동적으로 변화하며, 이를 추적하려면 전체 힙을 검사해야 함.
순환 참조 해결: 루트 집합에서 도달 가능한 객체를 그래프 탐색 방식으로 분석하므로 순환 참조 문제를 자연스럽게 해결.
효율적 메모리 관리: 일정 시점에 한 번에 많은 가비지를 수집함으로써 참조 변화 이벤트를 실시간으로 처리하는 오버헤드를 줄임.
메모리 최적화: 객체를 재배치하면서 조각화를 방지하고 힙 메모리 활용도를 높임.
Go GC 특징:
Concurrent Mark-and-Sweep 방식: 애플리케이션 실행 중에도 GC가 동작하며 "Stop-the-World" 시간을 최소화.
Write Barrier: 객체 참조 변경 시 변경된 데이터를 기록해 GC가 이를 효율적으로 처리하도록 보조.
특징: 저지연(Low Latency)과 짧은 멈춤 시간을 목표로 설계.
장점:
짧은 멈춤 시간: 실시간 시스템에서 적합.
효율적인 메모리 관리: 큰 힙에서도 안정적으로 작동.
단점:
CPU 사용량이 증가할 수 있으며, 완전히 자유로운 실시간 GC는 아님.
Write Barrier: 객체의 참조가 변경될 때 이를 기록해, GC가 변경된 부분만 효율적으로 스캔할 수 있도록 지원.
카드 테이블(Card Table): 메모리를 작은 블록(카드)으로 나누고, 각 카드가 수정되었는지 여부를 추적해 효율적으로 스캔.
장점:
변경된 객체나 영역만 스캔하므로 전체 힙을 검사하지 않아도 됨.
멀티스레드 환경에서 병렬 처리를 지원.
단점:
Write Barrier의 추가 작업이 애플리케이션 성능에 영향을 줄 수 있음.
순환 참조 문제를 해결하기 위한 개선된 레퍼런스 카운팅 알고리즘.
원리:
객체 그래프에서 트리 구조를 만들어 루트에서 접근 가능한 객체를 추적.
도달 불가능한 객체는 순환 참조에 상관없이 안전하게 수집.
장점:
순환 참조를 해결하며 실시간성 유지.
단점:
참조 그래프를 유지하고 관리하는 추가 비용 발생.
숨겨진 참조 경로: 힙 객체 간의 참조 관계를 감지할 수 없어 정확성이 떨어짐.
순환 참조: 레퍼런스 변수 변화만으로는 순환 참조를 해결할 수 없음.
멀티스레드 환경 문제: 여러 스레드가 참조를 변경하면 일관성을 유지하기 어려움.
참조 변화 이벤트 폭발: 변화가 많아질수록 관리 비용이 증가하며, 주기적으로 한꺼번에 처리하는 방식이 더 효율적일 수 있음.