쓰레기 수집 (컴퓨터 과학)
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
쓰레기 수집(Garbage collection, GC)은 프로그래밍 언어에서 메모리 관리를 자동화하는 기술이다. 이는 프로그래머가 직접 메모리 할당과 해제를 관리하는 대신, 가비지 컬렉터가 더 이상 사용되지 않는 메모리(도달 불가능한 메모리)를 자동으로 식별하고 회수하여 메모리 누수와 같은 오류를 방지하고 프로그램의 안정성을 높인다.
쓰레기 수집은 1959년 리스프(Lisp) 언어에서 처음 도입되었으며, 초기에는 "mark and sweep" 알고리즘을 사용했다. 이후 generational, incremental, concurrent 등의 다양한 알고리즘이 개발되어 성능과 응답성을 개선했다. 현대 프로그래밍 언어, 특히 자바(Java)와 .NET 등은 다양한 쓰레기 수집 알고리즘을 지원하며, 프로그램의 특성에 맞춰 메모리 관리를 최적화할 수 있도록 한다.
가비지 컬렉션은 프로그램의 주된 동작과는 별개로 수행되므로 오버헤드가 발생할 수 있으며, 프로그램 실행을 일시적으로 중단시키는 "스톱-더-월드" 방식과 병행하여 실행하는 "컨커런트" 방식으로 나뉜다. 주요 쓰레기 수집 전략으로는 참조 카운트, mark and sweep, 복사 GC, 세대별 GC 등이 있으며, 이들은 각기 다른 장단점을 가지고 있다. C/C++는 기본적으로 쓰레기 수집을 지원하지 않지만, Boehm GC와 같은 라이브러리를 통해 추가할 수 있다.
더 읽어볼만한 페이지
- 자동 메모리 관리 - 참조 횟수 계산 방식
참조 횟수 계산 방식은 객체 참조 횟수를 추적하여 미사용 객체를 회수하는 메모리 관리 기법으로, 참조 횟수가 0이 되면 객체를 해제하지만, 순환 참조, 성능 저하, 동기화 문제 등의 단점이 있으며, 파일 시스템, COM, 프로그래밍 언어, 위키백과 등에서 활용된다. - 자동 메모리 관리 - 통합과 집약
통합과 집약은 메모리 공간에서 사용 중인 영역과 빈 영역을 정리하여 시스템 효율성을 높이는 과정이지만, 단일 실패 지점, 확장성 문제, 보안 위험 등 여러 잠재적인 문제점을 야기할 수 있다. - 솔리드 스테이트 컴퓨터 저장 장치 - USB 플래시 드라이브
USB 플래시 드라이브는 USB 인터페이스를 통해 컴퓨터와 연결되는 휴대용 저장 장치로, 플래시 메모리 발명 후 상용화되어 플로피 디스크나 CD를 대체하며 데이터 저장, 운영 체제 부팅 등 다양한 용도로 사용되지만 위조 제품, 보안 위협, 제한적인 쓰기 횟수 등의 문제점도 있다. - 솔리드 스테이트 컴퓨터 저장 장치 - 솔리드 스테이트 드라이브
솔리드 스테이트 드라이브(SSD)는 반도체를 이용해 정보를 저장하는 장치로, 빠른 속도, 낮은 전력 소비, 적은 소음, 뛰어난 내구성을 특징으로 하며, 낸드 플래시 메모리를 기반으로 다양한 인터페이스를 통해 컴퓨터에 연결된다. - 메모리 관리 - 동적 메모리 할당
동적 메모리 할당은 프로그램 실행 중 힙 영역에서 메모리 공간을 확보 및 해제하여 효율적인 메모리 관리와 유연성을 제공하는 기술로, 메모리 누수 방지 및 가비지 컬렉션 등의 고려 사항이 중요하며 C, C++, C++/CLI, C# 등에서 사용된다. - 메모리 관리 - 정적 변수
정적 변수는 프로그램 실행 시간 동안 값을 유지하며, C 언어에서 `static` 키워드로 정의되어 함수 호출 간에 값을 유지하고, 객체 지향 프로그래밍에서 클래스의 모든 인스턴스에서 공유되는 클래스 변수로 사용된다.
쓰레기 수집 (컴퓨터 과학) | |
---|---|
가비지 컬렉션 (컴퓨터 과학) | |
종류 | 참조 횟수 세기 마크 앤 스위프 세대별 가비지 컬렉션 병렬 가비지 컬렉션 증분 가비지 컬렉션 실시간 가비지 컬렉션 |
사용 | 자바 닷넷 파이썬 자바스크립트 Lisp OCaml 루비 Go PHP |
관련 항목 | 메모리 누수 메모리 관리 힙 (자료 구조) 자동 메모리 관리 |
개요 | |
정의 | 더 이상 사용하지 않는 메모리 영역을 자동으로 회수하는 메모리 관리 기법 |
목적 | 메모리 누수를 방지하고 프로그래머의 메모리 관리 부담을 줄임 |
작동 방식 | 프로그램이 더 이상 사용하지 않는 객체를 식별 해당 객체가 차지하고 있던 메모리 공간을 운영체제에 반환 |
가비지 컬렉션 알고리즘 | |
참조 횟수 세기 (Reference Counting) | 각 객체에 대한 참조 횟수를 추적 참조 횟수가 0이 되면 가비지로 간주하고 회수 장점 구현이 간단하고 즉각적인 회수가 가능 단점 순환 참조 문제를 해결하기 어렵고, 오버헤드가 발생 가능 |
마크 앤 스위프 (Mark and Sweep) | 루트 객체부터 시작하여 연결된 객체를 탐색하며 'mark' 'mark'되지 않은 객체를 가비지로 간주하고 'sweep' 장점 순환 참조 문제를 해결 가능 단점 전체 메모리 공간을 탐색해야 하므로 성능 저하 발생 가능 |
세대별 가비지 컬렉션 (Generational Garbage Collection) | 객체의 수명 주기에 따라 세대를 구분하여 관리 어린 세대의 객체는 자주, 늙은 세대의 객체는 드물게 가비지 컬렉션 실행 장점 가비지 컬렉션 효율 향상 단점 세대 구분에 따른 오버헤드 발생 가능 |
병렬 가비지 컬렉션 (Parallel Garbage Collection) | 여러 스레드를 사용하여 가비지 컬렉션을 병렬로 수행 장점 가비지 컬렉션 시간 단축 단점 스레드 동기화 문제 발생 가능 |
증분 가비지 컬렉션 (Incremental Garbage Collection) | 가비지 컬렉션 작업을 작은 단위로 나누어 수행 장점 프로그램 실행 중단 시간을 최소화 단점 구현이 복잡하고 오버헤드 발생 가능 |
실시간 가비지 컬렉션 (Real-time Garbage Collection) | 가비지 컬렉션으로 인한 프로그램 지연 시간을 예측 가능하도록 보장 장점 실시간 시스템에 적합 단점 구현이 매우 복잡하고 오버헤드 발생 가능 |
가비지 컬렉션 구현 | |
언어 지원 | 자바 닷넷 파이썬 자바스크립트 Lisp OCaml 루비 Go PHP 등 다양한 프로그래밍 언어에서 자동 가비지 컬렉션 지원 |
메모리 압축 (Memory Compaction) | 가비지 컬렉션 후 흩어진 메모리 조각들을 모아 연속적인 공간을 확보하는 기술 메모리 할당 효율을 높이고 외부 단편화를 줄이는 데 기여 |
고려 사항 | |
성능 | 가비지 컬렉션 빈도와 실행 시간은 프로그램 성능에 큰 영향 |
메모리 관리 | 메모리 누수를 방지하는 것이 중요 |
알고리즘 선택 | 프로그램의 특성에 맞는 가비지 컬렉션 알고리즘을 선택해야 함 |
장단점 | |
장점 | 메모리 누수 방지 프로그래머의 메모리 관리 부담 감소 프로그램 안정성 향상 |
단점 | 가비지 컬렉션으로 인한 성능 저하 가능성 메모리 사용량 예측의 어려움 개발자가 직접 메모리 관리를 제어하기 어려움 |
2. 역사적 배경
쓰레기 수집 기술은 프로그래밍 언어 역사에서 메모리 관리를 자동화하려는 시도에서 비롯되었다. 초기의 컴퓨터 시스템은 메모리 자원이 매우 제한적이었기 때문에, 프로그래머는 메모리 할당과 해제를 직접 관리해야 했다. 이러한 수동 메모리 관리는 오류 발생 가능성이 높았고, 프로그램의 안정성을 저해하는 주요 원인이 되었다.
리스프는 1959년에 존 매카시가 개발한 프로그래밍 언어로, 자동 메모리 관리, 즉 쓰레기 수집을 최초로 도입한 언어 중 하나였다. 리스프는 프로그램 실행 중에 더 이상 사용되지 않는 메모리 블록을 자동으로 식별하고 회수하여 재사용할 수 있도록 함으로써, 프로그래머가 메모리 관리에 쏟아야 하는 부담을 크게 줄였다. 리스프의 쓰레기 수집기는 "mark and sweep" 알고리즘의 초기 형태를 사용했는데, 이 알고리즘은 메모리 내의 모든 객체를 검사하여 사용 중인 객체를 표시(mark)하고, 표시되지 않은 객체를 메모리에서 해제(sweep)하는 방식으로 작동한다.
초기 쓰레기 수집 기술은 성능상의 제약이 있었다. "mark and sweep" 알고리즘은 프로그램 실행을 일시적으로 중단시키고 전체 메모리를 검사해야 했기 때문에, 프로그램의 응답성을 떨어뜨리는 원인이 되기도 했다. 이러한 문제를 해결하기 위해 다양한 쓰레기 수집 알고리즘이 개발되었는데, 대표적인 예로는 generational garbage collection, incremental garbage collection, concurrent garbage collection 등이 있다.
- generational garbage collection: 객체의 생존 기간에 따라 메모리 영역을 분리하여 관리함으로써, 더 자주 발생하는 짧은 생존 기간의 객체에 대한 쓰레기 수집을 최적화한다.
- incremental garbage collection: 전체 메모리를 한 번에 검사하는 대신, 작은 단위로 나누어 점진적으로 쓰레기 수집을 수행함으로써 프로그램의 일시 중단 시간을 줄인다.
- concurrent garbage collection: 프로그램 실행과 동시에 쓰레기 수집을 수행함으로써, 프로그램의 응답성을 최대한 유지한다.
현대의 프로그래밍 언어, 예를 들어 자바, 닷넷 등은 더욱 발전된 형태의 쓰레기 수집 기술을 제공한다. 이러한 언어들은 다양한 쓰레기 수집 알고리즘을 선택적으로 사용할 수 있도록 지원하며, 프로그램의 성능 요구 사항에 맞춰 메모리 관리를 최적화할 수 있도록 한다. 예를 들어, 자바의 HotSpot VM은 다양한 GC 알고리즘(Serial GC, Parallel GC, CMS GC, G1 GC 등)을 제공하여, 애플리케이션의 특성에 맞게 선택할 수 있도록 한다.
쓰레기 수집 기술은 현대 프로그래밍에서 매우 중요한 역할을 수행하며, 메모리 누수와 같은 문제를 예방하고 프로그램의 안정성을 높이는 데 기여한다. 또한, 프로그래머가 메모리 관리에 대한 부담 없이 프로그램의 핵심 로직에 집중할 수 있도록 함으로써, 생산성 향상에도 기여한다.
3. 작동 원리
가비지 컬렉션은 프로그램 실행 중 메모리 관리를 자동화하는 기술이다. 기존 메모리 관리 방식에서는 프로그래머가 프로그램 내에서 메모리 할당 및 해제를 직접 관리해야 했다. 가비지 컬렉션을 사용하면 메모리 확보는 프로그래머가 명시적으로 수행하지만, 해제는 가비지 컬렉터가 자동으로 처리한다. 가비지 컬렉터는 프로그램이 더 이상 사용하지 않는 메모리, 즉 도달 불가능한 메모리를 식별하여 회수한다. 이때 스택이나 변수 테이블 등으로부터의 참조를 추적하여 메모리 접근 가능성을 판단한다.
가비지 컬렉션은 프로그래밍 언어, 언어 처리 시스템, 프레임워크에 내장되거나 외부 라이브러리 형태로 제공될 수 있다. 자바는 언어 사양에 가비지 컬렉션이 표준적으로 내장되어 있지만, C 언어나 C++(C++)에서는 Boehm GC/Boehm garbage collector영어와 같은 라이브러리나 스마트 포인터를 통해 가비지 컬렉션을 구현할 수 있다.
가비지 컬렉션은 프로그램의 주된 동작과는 별개로 수행되는 작업이므로, 프로그램 성능에 미치는 영향을 최소화하는 것이 중요하다. 데스크톱 애플리케이션에서는 짧은 응답 시간을 위해 가비지 컬렉션으로 인한 프로그램 정지 시간을 줄이는 것이 중요하며, 서버 애플리케이션에서는 처리량을 높이는 것이 중요하다. 임베디드 시스템에서는 리소스 소비를 줄이는 것이 중요하며, 실시간 시스템에서는 프로그램 동작 시간의 편차를 최소화해야 한다. 이러한 다양한 요구 사항을 충족하기 위해 여러 가비지 컬렉션 알고리즘이 개발되었다.
대표적인 가비지 컬렉션 알고리즘은 다음과 같다.
- 참조 카운트: 객체를 참조하는 포인터 수를 세어 0이 되면 해제하는 방식이다. 하지만 순환 참조 문제를 해결하지 못하고, 해제가 집중될 경우 정지 시간이 길어질 수 있다.
- 마크 앤 스위프: 객체에서 다른 객체로의 참조를 따라가 도달할 수 없는 객체를 제거하는 방식이다.
- 복사 GC: 현재 사용 중인 메모리 영역 외에 동일한 크기의 메모리 영역을 추가로 준비하여, 가비지 컬렉션 시점에 유효한 객체만 다른 영역으로 복사하는 방식이다. 이 방식은 메모리 공간을 2배로 사용하고 객체의 주소가 변경될 수 있지만, 가비지 컬렉션과 메모리 압축을 동시에 수행할 수 있다는 장점이 있다.
이러한 알고리즘들은 단독으로 사용되기도 하지만, 세대별 가비지 컬렉션처럼 복합적으로 사용되기도 한다.
가비지 컬렉션은 애플리케이션 동작에 미치는 영향에 따라 '''스톱-더-월드 방식'''과 '''컨커런트 방식'''으로 나눌 수도 있다.[13] 스톱-더-월드 방식은 가비지 컬렉션을 수행하기 위해 애플리케이션의 모든 동작을 멈추는 반면, 컨커런트 방식은 애플리케이션 동작과 병행하여 가비지 컬렉션을 수행한다.
가비지 컬렉션은 프로그래머가 메모리를 직접 해제하는 수고를 덜어주어 여러 종류의 오류를 방지하는 데 도움이 된다.[6] 대표적으로 다음과 같은 오류들을 예방할 수 있다.
- 댕글링 포인터: 메모리가 해제되었지만 해당 메모리를 가리키는 포인터가 남아있어 발생하는 오류이다. 이 포인터가 역참조될 경우 예측 불가능한 결과를 초래할 수 있다.[7]
- 이중 해제 버그: 이미 해제된 메모리 영역을 다시 해제하려 할 때 발생하는 오류이다.
- 메모리 누수: 프로그램이 더 이상 사용하지 않는 객체의 메모리를 해제하지 못해 메모리 고갈로 이어지는 오류이다.[8]
가비지 컬렉션은 프로그래머가 명시적으로 메모리 해제를 할 필요가 없으므로 메모리 관리 관련 버그 발생 가능성을 줄여준다. 또한, 객체와 포인터를 관리함으로써 객체는 존재하지만 이를 가리키는 포인터가 없는 상태를 방지하고, 객체의 이중 해제 및 무효 포인터 사용을 방지한다.
가비지 컬렉션은 메모리 관리 관련 버그를 줄이는 것 외에도 프로그래밍 스타일의 선택지를 넓혀준다. 형변환을 위한 임시 객체 생성이나 멀티 스레드 환경에서 객체 공유와 같이 복잡한 메모리 관리 기술이 필요한 경우, 가비지 컬렉션을 통해 이러한 복잡성을 줄이고 코드를 더 자연스럽게 작성할 수 있다.
하지만 가비지 컬렉션은 메모리 해제를 결정하기 위해 컴퓨팅 자원을 사용하므로 오버헤드가 발생하여 프로그램 성능을 저하시킬 수 있다. 일부 연구에서는 가비지 컬렉션이 명시적 메모리 관리 방식만큼 빠르게 실행되려면 더 많은 메모리가 필요하다는 결론을 내리기도 했다. 또한 가비지 컬렉션 시점을 예측하기 어려워 프로그램이 일시적으로 멈추는 현상이 발생할 수 있으며, 이는 실시간 환경이나 대화형 프로그램에서 문제가 될 수 있다. 애플은 이러한 성능 문제를 이유로 iOS에서 가비지 컬렉션을 채택하지 않았다.
가비지 컬렉션의 동작 타이밍을 예측하기 어렵기 때문에, 데드라인이 정해져 있는 실시간 시스템에 사용하는 것은 어렵다. 이러한 문제를 해결하기 위해 증분 GC나 동시 GC와 같이 실시간성을 개선한 가비지 컬렉션 기술이 개발되고 있다.
가비지 컬렉션을 사용하더라도 더 이상 사용하지 않는 객체에 대한 포인터를 계속 유지하는 코드는 메모리 누수를 발생시킬 수 있다. 이는 가비지 컬렉션이 해결할 수 없는 논리적인 설계 문제이다. 많은 가비지 컬렉션 구현체는 가비지 컬렉션이 시작되면 다른 처리를 중단하는 "Stop-the-world" 방식을 사용하며, 이로 인해 CPU를 장시간 점유하는 경우가 발생할 수 있다.
4. 장단점
쓰레기 수집 기술은 메모리 관리를 자동화하여 프로그래머의 부담을 줄여주지만, 몇 가지 장단점을 가지고 있다.
장점쓰레기 수집이 지원되는 환경에서는 프로그래머가 동적으로 할당한 메모리 영역을 완벽하게 관리할 필요가 없어진다. 이는 다음과 같은 종류의 오류를 줄이거나 완전히 막을 수 있다.[6]
- 유효하지 않은 포인터 접근 (댕글링 포인터): 이미 해제된 메모리에 접근하는 버그를 방지한다. 해제된 메모리가 다른 용도로 재할당되었을 경우, 잘못된 값을 읽어오는 문제를 막을 수 있다.[7]
- 이중 해제: 이미 해제된 메모리를 다시 해제하는 버그를 방지한다.
- 메모리 누수: 더 이상 필요하지 않은 메모리가 해제되지 않고 남아있는 버그를 방지한다. 다만, 가비지 컬렉션에서도, 앞으로 사용할 일이 없는 객체에 대한 포인터를 언제까지나 유지하고 있는 코드에서는, 언제까지나 객체가 해제되지 않아 메모리 부족이 발생해 버린다. 이는 논리적인 설계 문제이며, 쓰레기 수집을 사용하는 환경에서도 발생할 수 있다. 객체 (데이터를 저장한 메모리 영역)와 그것을 가리키는 포인터를 관리하므로, 객체는 존재하지만 그것을 가리키는 포인터가 없는 상태를 회피할 수 있다.
메모리 관리에 관한 버그를 회피하는 것 외에도, 프로그래밍 스타일의 선택지를 넓히는 효과도 가진다. 형변환 등을 위해 일시적인 객체를 생성하거나, 멀티 스레드를 이용한 프로그램에서 스레드 간에 객체를 공유하는 등의 복잡한 메모리 관리 기술을 보다 자연스럽게 사용할 수 있도록 돕는다.
단점반면, 쓰레기 수집은 다음과 같은 단점을 가지고 있다.
- 오버헤드: 어떤 메모리를 해제할지 결정하는 데 컴퓨팅 자원이 소모된다. 객체가 더 이상 필요 없어지는 시점을 프로그래머가 미리 알고 있는 경우에도 쓰레기 수집 알고리즘이 메모리 해제 시점을 추적해야 하므로 오버헤드가 발생하여 프로그램 성능을 저하시킬 수 있다. 2005년에 발표된 논문에 따르면, 쓰레기 수집은 이 오버헤드를 보상하고 명시적 메모리 관리를 사용하는 프로그램만큼 빠르게 실행하기 위해 5배의 메모리가 필요하다는 결론을 내렸다. 하지만 이 비교는 프로파일러에서 실행된 프로그램의 추적을 수집하여 구현된 오라클을 사용하여 할당 해제 호출을 삽입하여 생성된 프로그램과 비교한 것으로, 프로그램은 프로그램의 특정 실행에 대해서만 올바르게 동작한다. 메모리 계층 효과와의 상호 작용은 예측하기 어려운 상황에서 오버헤드를 발생시킬 수 있다. 애플은 이러한 성능 문제를 이유로 iOS에서 쓰레기 수집을 채택하지 않았다.
- 예측 불가능한 실행 중단: 쓰레기 수집이 일어나는 타이밍이나 점유 시간을 미리 예측하기 어렵다. 많은 구현에서는, 뒤섞임으로 인해 잘못하여 도달 가능한 메모리가 불가능하다고 판단되지 않도록, 가비지 컬렉트가 시작되면 다른 처리를 멈추고, 본 처리가 중단된다 (Stop-the-world 가비지 컬렉터). CPU를 장시간 (수백 밀리초에서 수십 초) 점유하는 경우도 있다. 이 때문에 프로그램이 예측 불가능하게 일시적으로 정지할 수 있다. 이러한 특성은 특히 실시간 시스템에는 적합하지 않다. 실시간성을 개선한 GC로, 증분 GC나 동시 GC가 있다. 예측 불가능한 중단은 실시간 환경, 트랜잭션 처리, 또는 대화형 프로그램에서 문제가 될 수 있다. 점진적, 동시적, 실시간 쓰레기 수집기는 이러한 문제를 해결하기 위해 다양한 시도를 하고 있다.
- 자원 해제 시점 통제 불가능: 할당된 메모리가 해제되는 시점을 알 수 없다. 자원 할당과 변수 초기화를 일치시키는 RAII(Resource Acquisition is Initialization) 스타일의 프로그래밍에서는, 자원 해제 시점을 알 수 없다는 것을 의미한다.
4. 1. 장점
가비지 컬렉션(GC)은 프로그래머가 메모리를 수동으로 해제하는 수고를 덜어준다. 이는 다음과 같은 종류의 오류를 방지하는 데 도움이 된다.[6]- 메모리 조각이 해제되었지만 해당 메모리를 가리키는 포인터가 여전히 존재하고, 그 중 하나가 역참조될 때 발생하는 ''댕글링 포인터''. 이렇게 되면 메모리가 다른 용도로 재할당되었을 수 있으며, 예측할 수 없는 결과가 발생할 수 있다.[7]
- 프로그램이 이미 해제된 메모리 영역을 다시 해제하려 할 때 발생하는 ''이중 해제 버그''.
- 프로그램이 접근 불가능해진 객체가 차지하는 메모리를 해제하지 못하여 메모리 고갈로 이어질 수 있는 특정 종류의 ''메모리 누수''.[8]
가비지 컬렉션은 프로그래머가 명시적으로 메모리 해제를 할 필요가 없으므로, 메모리 관리에 관련된 오류가 발생하기 쉬운 버그를 회피할 수 있다.
- 메모리 누수 회피: 가비지 컬렉션은 객체(데이터를 저장한 메모리 영역)와 그것을 가리키는 포인터를 관리하므로, 객체는 존재하지만 그것을 가리키는 포인터가 없는 상태를 회피할 수 있다.
- 객체의 이중 해제 회피: 일단 해제한 객체를 다시 해제하는 것을 방지한다.
- 무효 포인터 회피: 예를 들어, 메모리를 동적으로 할당하는 서브루틴(C 언어의 `malloc` 함수나 파스칼의 `New` 프로시저 등)으로 확보한 메모리 영역을 가리키는 포인터였지만, 메모리를 해제하는 서브루틴 (C 언어의 `free` 함수나 파스칼의 `Dispose` 프로시저 등)에 넘겨 해제한 직후의 포인터나, 서브루틴 내의 자동 변수(비정적인 지역 변수)의 주소를 가리키는 포인터였지만, 반환값 등으로 인해 잘못하여 서브루틴의 호출 측에 반환되어 버린 포인터 등이 해당된다. 이러한 포인터에는 어떤 주소가 대입되어 있지만, 그 주소에는 유효한 객체가 이미 존재하지 않고, 포인터는 무효한 메모리 주소를 가리키고 있다. 이러한 무효한 포인터를 댕글링 포인터라고 하며, 가비지 컬렉션은 이 문제를 회피한다.
다만 가비지 컬렉션에서도, 앞으로 사용할 일이 없는 객체에 대한 포인터를 언제까지나 유지하고 있는 코드에서는, 객체가 해제되지 않아 메모리 부족이 발생할 수 있다. 이는 논리적인 설계의 문제이며, 가비지 컬렉션을 가진 처리계에서도 이 종류의 메모리 누수는 발생한다.
메모리 관리에 관한 버그를 회피하는 것 외에도, 프로그래밍 스타일의 선택지를 넓히는 효과도 가진다. 형변환 등을 위해 일시적인 객체를 생성하거나, 멀티 스레드를 이용한 프로그램에서 스레드 간에 객체를 공유하여 사용하는 등의 처리는 메모리 확보·해제 처리의 기술이 복잡해지는 경우가 많다. 그러나, 가비지 컬렉션을 가진 언어 처리계에서는 복잡한 기술을 생략할 수 있어, 이러한 처리를 더욱 자연스럽게 기술할 수 있다.
4. 2. 단점
쓰레기 수집은 해제할 메모리를 결정하기 위해 컴퓨팅 자원을 사용하므로, 오버헤드가 발생하여 프로그램 성능을 저하시킬 수 있다. 2005년 발표된 논문에서는 쓰레기 수집이 이 오버헤드를 보상하고 명시적 메모리 관리를 사용하는 프로그램만큼 빠르게 실행하기 위해 5배의 메모리가 필요하다는 결론을 내렸다. 하지만 이 비교는 프로파일러에서 실행된 프로그램의 추적을 수집하여 구현된 오라클을 사용하여 할당 해제 호출을 삽입하여 생성된 프로그램과 비교한 것으로, 프로그램은 프로그램의 특정 실행에 대해서만 올바르게 동작한다. 메모리 계층 효과와의 상호 작용은 예측하기 어려운 상황에서 오버헤드를 발생시킬 수 있다. 애플은 이러한 성능 문제를 이유로 iOS에서 쓰레기 수집을 채택하지 않았다.쓰레기 수집 시점을 예측할 수 없어 프로그램이 일시적으로 멈추는 현상이 발생할 수 있다. 예측 불가능한 중단은 실시간 환경, 트랜잭션 처리, 또는 대화형 프로그램에서 문제가 될 수 있다. 점진적, 동시적, 실시간 쓰레기 수집기는 이러한 문제를 해결하기 위해 다양한 시도를 하고 있다.
쓰레기 수집은 프로그래머가 명시적으로 메모리 해제를 할 필요가 없도록 하여 메모리 누수, 객체의 이중 해제, 무효 포인터 등의 버그를 회피할 수 있게 한다. 하지만 앞으로 사용할 일이 없는 객체에 대한 포인터를 계속 유지하고 있는 코드에서는 객체가 해제되지 않아 메모리 부족이 발생할 수 있다.
많은 구현에서 가비지 컬렉트가 시작되면 다른 처리를 멈추고, 본 처리가 중단된다 (Stop-the-world 가비지 컬렉터). CPU를 장시간 점유하는 경우도 있다. 가비지 컬렉션의 동작 타이밍 예측이 어렵기 때문에, 데드라인이 정해져 있는 실시간 시스템에 사용하는 것은 어렵다. 실시간성을 개선한 GC로, 증분 GC나 동시 GC가 있다.
5. 쓰레기 수집 전략
쓰레기 수집 전략에는 다양한 알고리즘과 기법이 존재하며, 각각의 특징과 장단점을 가진다.
대부분의 쓰레기 수집 기법은 포인터 추적 방식을 사용한다. 이는 한 개 이상의 변수가 접근 가능한 메모리는 앞으로 사용할 수 있는 메모리로 간주하고, 그 밖의 메모리를 해제하는 방식이다. 접근 가능한 객체는 변수가 직접 가리키는 메모리 또는 간접적으로 가리키는 메모리를 의미하며, 변수에는 콜 스택에서 정의된 지역 변수와 전역 변수가 모두 포함된다. 접근 가능한 메모리는 재귀적으로 정의될 수 있는데, 변수가 가리키는 객체와 접근 가능한 객체가 가리키는 모든 객체는 접근 가능한 것으로 간주된다.
다음은 여러 가지 포인터 추적 기법들이다. 몇몇 언어에서는 아래 나열된 기법들 중 여러 전략을 함께 사용하기도 한다.
- 표식-소거 가비지 컬렉션: 메모리에서 접근 가능한 모든 객체를 "표식"한 후, 메모리 전체를 훑으면서 표식되지 않은 객체들을 "소거"하는 방식이다. 순환 참조를 해결할 수 있지만, 모든 메모리를 훑어야 하므로 비용이 많이 들 수 있다.
- 참조 횟수: 각 객체가 얼마나 많은 참조를 가지고 있는지 추적하는 방식이다. 객체에 대한 참조가 없어지면 즉시 메모리에서 해제된다. 구현이 간단하고 즉각적으로 쓰레기를 회수할 수 있지만, 순환 참조를 해결하지 못한다는 단점이 있다. 파이썬 표준 구현인 CPython에서 이 방식을 사용하며, C++에서는 스마트 포인터라는 특수한 객체를 이용해 이 기법을 구현할 수 있다. 객체가 접근 불가능해지는 즉시 메모리가 해제되므로, 프로그래머가 객체의 해제 시점을 어느 정도 예측할 수 있다는 장점이 있다. 또한, 객체가 사용된 직후에 메모리를 해제하므로, 메모리 해제 시점에 해당 객체는 CPU 캐시에 저장되어 있을 확률이 높아 메모리 해제가 빠르게 이루어진다. 이러한 장점 때문에 참조 횟수 계산 방식은 메모리 관리 뿐 아니라 다른 자원 할당 기법에도 종종 사용된다. 하지만 두 개 이상의 객체가 서로를 가리키고 있을 경우, 참조 횟수가 0이 되지 않아 메모리 누수의 원인이 되는 순환 참조 문제가 발생할 수 있다. CPython은 이 문제를 해결하기 위해 순환 참조를 감지하는 알고리즘을 사용하며, 자료구조에서 약한 참조(참조 횟수를 증가시키지 않는 포인터)를 사용하여 이 문제를 해결할 수도 있다. 멀티스레드 환경에서는 스레드간에 공유하는 객체의 참조 횟수 계산을 위해 원자적 명령을 사용하거나 락을 걸어야 한다는 단점이 있다. 이를 회피하기 위해 스레드 단위 지역 변수로 참조 횟수를 따로 관리하면서, 스레드의 참조 횟수가 0이 될 때만 전역 참조 횟수를 확인하는 방식을 사용할 수 있다. 리눅스 커널에서 이 방식을 사용한다. 또한, 참조 횟수가 0이 될 때 해당 객체가 가리키는 다른 객체들 또한 동시에 0으로 만드는 작업이 일어나는 과정은 경우에 따라 많은 시간이 걸릴 수도 있기 때문에 실시간 시스템에는 적합하지 않을 수 있다.
- 복사 가비지 컬렉션: 메모리를 두 개의 공간으로 나누어, 한 공간이 꽉 차면 다른 공간으로 살아있는 객체들을 복사한다. 이 과정에서 쓰레기는 자연스럽게 제거된다. 빠른 속도를 낼 수 있지만, 메모리 사용량이 두 배로 늘어난다는 단점이 있다.
- 세대별 가비지 컬렉션: 객체의 수명이 짧다는 경험적 관찰에 기반하여, 메모리를 여러 세대로 나누어 더 어린 세대의 객체에 더 자주 가비지 컬렉션을 수행한다. 가비지 컬렉션의 효율성을 높일 수 있으며, 자바의 HotSpot VM이 세대별 가비지 컬렉션을 사용한다. 기존의 GC는 대상 메모리 영역이 가득 찼을 때 한꺼번에 GC를 수행하는 방식인데, 이 방법은 메모리 영역의 크기가 커짐에 따라 GC 시간이 길어지는 단점이 있다. 세대별 GC에서는 신규 영역과 오래된 영역으로 메모리 영역이 나뉘며, 새로 생성된 객체는 신규 영역에 배치되고, 신규 영역이 가득 차면 신규 영역 내부만의 GC가 실행된다. 이 GC는 메모리 전체에 대한 GC에 비해 당연히 부하가 적고 빠르다. 신규 영역에 대한 GC를 일정 횟수 살아남은 객체는 오래된 영역으로 이동하고, 오래된 영역이 가득 찼을 때 처음으로 모든 메모리 영역을 대상으로 하는 FULL GC가 수행된다.
- 정지점 가비지 컬렉션: 객체를 이동시킬 수 없는 환경에서 사용된다. 객체를 이동시키지 않고 메모리 내에서 쓰레기를 회수하는 방식으로, 메모리 단편화 문제를 야기할 수 있다.
각 기법들은 특정 환경과 요구 사항에 따라 장단점을 가진다. 따라서 프로그래밍 언어와 시스템은 이러한 기법들을 조합하거나 변형하여 사용함으로써 효율적인 메모리 관리를 달성할 수 있다.
이스케이프 분석은 힙 할당을 스택 할당으로 변환하여 수행해야 하는 가비지 수집의 양을 줄일 수 있는 컴파일 타임 기술이다. 이 분석은 함수 내에서 할당된 객체가 함수 외부에서 접근 가능한지 여부를 결정한다. 함수 지역 할당이 다른 함수 또는 스레드에서 접근 가능한 것으로 확인되면, 할당이 "이스케이프"되었다고 하며 스택에서 수행될 수 없다. 그렇지 않으면 객체는 스택에 직접 할당되어 함수가 반환될 때 해제될 수 있으며, 힙과 관련된 메모리 관리 비용을 우회한다.
가비지 컬렉션은 자바처럼 언어 사양 및 언어 처리계(런타임)에 표준적으로 내장된 것을 투명하게 이용하는 형태가 대부분이다. 그러나 C 언어나 C++처럼 언어 사양 및 언어 처리계에는 존재하지 않더라도 Boehm GCBoehm 가비지 컬렉터/Boehm garbage collector영어 또는 각종 스마트 포인터와 같은 라이브러리로 구현된 것을 이용할 수도 있다.
가비지 컬렉션은 프로그램 본래의 동작과는 별도로 시간이 걸리는 처리이므로, 본래의 프로그램 동작에 대한 영향이 적은 것이 요구된다. 일반적으로 데스크톱 애플리케이션에서는 응답 시간을 단축하기 위해 가비지 컬렉션에 의한 프로그램 정지 시간을 최소화하는 것이 요구된다. 또한 서버 애플리케이션에서는 응답 시간보다 처리량이 요구되는 경우가 많아 가비지 컬렉션에도 처리량 성능이 높은 것이 요구된다. 게다가 기기 임베디드 애플리케이션에서는 기기에 탑재되는 CPU의 능력 부족이나 메모리 용량 부족으로 인해 리소스 소비가 적은 것이 요구된다. 또한 실시간 시스템에서는 프로그램 동작 시간의 편차를 최소화하고 싶다는 요구도 있다. 이러한 요구를 모두 충족하는 알고리즘은 존재하지 않으므로 다양한 방법이 제안되고 있다.
애플리케이션 동작에 미치는 영향의 관점에서 애플리케이션 동작을 모두 멈추는 '''스톱-더-월드 방식'''과 애플리케이션 동작과 병행하여 동작하는 '''컨커런트 방식'''으로 분류할 수 있다.[13]
5. 1. 포인터 추적 방식
대부분의 쓰레기 수집 기법은 포인터 추적 방식을 사용한다. 포인터 추적 방식은 한 개 이상의 변수가 접근 가능한 메모리는 앞으로 사용할 수 있는 메모리로 간주하고, 그 밖의 메모리를 해제하는 방식을 가리킨다.추적 가비지 컬렉션은 가비지 컬렉션의 가장 일반적인 유형으로, "가비지 컬렉션"은 참조 카운팅과 같은 다른 방식보다 추적 가비지 컬렉션을 지칭하는 경우가 많다. 전체적인 전략은 특정 루트 객체로부터의 참조 체인을 통해 어떤 객체에 "도달"할 수 있는지 추적하여 가비지 수집해야 하는 객체를 결정하고, 나머지를 가비지로 간주하여 수집하는 것이다. 그러나 구현에 사용되는 알고리즘은 매우 다양하며 복잡성과 성능 특성이 광범위하게 다르다.
5. 1. 1. 접근 가능한 객체
접근 가능한 객체는 어떤 변수가 직접 가리키는 메모리, 또는 간접적으로 가리키는 메모리를 의미한다. 여기서 변수는 콜 스택에서 정의된 지역 변수와 전역 변수를 모두 포함한다. 접근 가능한 메모리는 다음과 같이 재귀적으로 정의할 수 있다.# 변수가 가리키는 객체
# 접근 가능한 객체가 가리키는 모든 객체는 마찬가지로 접근 가능하다.
5. 1. 2. 여러 가지 포인터 추적 기법
다음은 여러 가지 포인터 추적 기법들이다. 몇몇 언어에서는 아래 나열된 기법들 중 여러 전략을 함께 사용하기도 한다.- 표식-소거(mark-and-sweep) 가비지 컬렉션: 이 방법은 먼저 메모리에서 접근 가능한 모든 객체를 "표식"한다. 그 후, 메모리 전체를 훑으면서 표식되지 않은 객체들을 "소거"한다. 표식-소거는 순환 참조를 해결할 수 있지만, 모든 메모리를 훑어야 하므로 비용이 많이 들 수 있다.
- 참조 횟수(reference counting): 각 객체가 얼마나 많은 참조를 가지고 있는지 추적한다. 객체에 대한 참조가 없어지면, 즉시 메모리에서 해제된다. 이 방법은 구현이 간단하고 즉각적으로 쓰레기를 회수할 수 있지만, 순환 참조를 해결하지 못한다는 단점이 있다. 파이썬이 참조 횟수 방식을 사용한다.
- 복사 가비지 컬렉션(copying garbage collection): 메모리를 두 개의 공간으로 나누어, 한 공간이 꽉 차면 다른 공간으로 살아있는 객체들을 복사한다. 이 과정에서 쓰레기는 자연스럽게 제거된다. 복사 가비지 컬렉션은 빠른 속도를 낼 수 있지만, 메모리 사용량이 두 배로 늘어난다는 단점이 있다.
- 세대별 가비지 컬렉션(generational garbage collection): 객체의 수명이 짧다는 경험적 관찰에 기반한다. 메모리를 여러 세대로 나누어, 더 어린 세대의 객체에 더 자주 가비지 컬렉션을 수행한다. 이 방법은 가비지 컬렉션의 효율성을 높일 수 있다. 자바의 HotSpot VM이 세대별 가비지 컬렉션을 사용한다.
- 정지점 가비지 컬렉션(immovable garbage collection): 객체를 이동시킬 수 없는 환경에서 사용된다. 객체를 이동시키지 않고 메모리 내에서 쓰레기를 회수하는 방식으로, 메모리 단편화 문제를 야기할 수 있다.
각 기법들은 특정 환경과 요구 사항에 따라 장단점을 가진다. 따라서 프로그래밍 언어와 시스템은 이러한 기법들을 조합하거나 변형하여 사용함으로써 효율적인 메모리 관리를 달성할 수 있다.
5. 2. 참조 횟수 계산 방식
참조 횟수 계산 방식은 각 객체가 자신을 참조하는 횟수를 기록하여, 참조 횟수가 0이 되면 해당 객체를 해제하는 방식이다. 파이썬 표준 구현인 CPython에서 이 방식을 사용한다. C++에서는 스마트 포인터라는 특수한 객체를 이용해 이 기법을 구현할 수 있다.참조 횟수 계산 방식은 다음과 같은 장점을 가진다.
- 객체가 접근 불가능해지는 즉시 메모리가 해제되므로, 프로그래머가 객체의 해제 시점을 어느 정도 예측할 수 있다.
- 객체가 사용된 직후에 메모리를 해제하므로, 메모리 해제 시점에 해당 객체는 캐시에 저장되어 있을 확률이 높다. 따라서 메모리 해제가 빠르게 이루어진다.
이러한 장점 때문에, 참조 횟수 계산 방식은 메모리 관리뿐 아니라 다른 자원 할당 기법에도 종종 사용된다. 예를 들어 하드 디스크 블록의 할당과 해제를 담당하는 파일 시스템의 경우, 포인터 추적 방식의 쓰레기 수집은 디스크라는 매체의 특성상 오랜 시간을 소모하게 된다. 그러나 참조 횟수 계산 방식은 할당된 블록을 해제하는 시점에 해당 블록을 가리키는 포인터가 운영 체제의 버퍼에 로딩되어 있으므로, 빠르게 블록을 해제할 수 있다.
반면 참조 횟수 계산 방식에는 다음과 같은 단점이 있다.
- 두 개 이상의 객체가 서로를 가리키고 있을 경우, 참조 횟수가 0이 되지 않게 된다. 이를 순환 참조라고 하며, 메모리 누수의 원인이 된다. CPython은 이 문제를 해결하기 위해 순환 참조를 감지하는 알고리즘을 사용한다. 또한 자료구조에서 약한 참조(참조 횟수를 증가시키지 않는 포인터)를 사용하여 이 문제를 해결할 수도 있다.
- 다중 스레드 환경에서는, 스레드 간에 공유하는 객체의 참조 횟수 계산을 위해 원자적 명령을 사용하거나 락을 걸어야 한다. 이 문제를 회피하기 위해 스레드 단위 지역 변수로 참조 횟수를 따로 관리하면서, 스레드의 참조 횟수가 0이 될 때만 전역 참조 횟수를 확인하는 방식을 사용할 수 있다. 리눅스 커널에서 이 방식을 사용한다.
- 참조 횟수가 0이 될 때, 해당 객체가 가리키는 다른 객체들 또한 동시에 0으로 만드는 작업이 일어난다. 이 과정은 경우에 따라 많은 시간이 걸릴 수도 있기 때문에 실시간 시스템에는 적합하지 않을 수 있다.
참조 계수 가비지 컬렉션은 각 객체가 자신에 대한 참조 수를 가지고 있는 방식이다. 가비지는 참조 계수가 0인 객체로 식별된다. 객체의 참조 계수는 해당 객체에 대한 참조가 생성될 때 증가하고 참조가 소멸될 때 감소한다. 계수가 0이 되면 객체의 메모리가 회수된다.
수동 메모리 관리와 마찬가지로, 추적 가비지 컬렉션과는 달리, 참조 계수는 객체의 마지막 참조가 소멸되는 즉시 객체가 소멸되도록 보장하며, 일반적으로 CPU 캐시 내의 메모리, 해제될 객체 내의 메모리 또는 해당 객체가 직접 가리키는 메모리에만 접근하므로 CPU 캐시 및 가상 메모리 작동에 심각한 부정적인 부작용을 일으키는 경향이 없다.
참조 계수에는 여러 가지 단점이 있으며, 이는 일반적으로 더 정교한 알고리즘으로 해결하거나 완화할 수 있다.
- 순환 참조: 두 개 이상의 객체가 서로를 참조하는 경우, 상호 참조로 인해 순환 참조가 생성되어 참조 계수가 0이 되지 않아 아무것도 수집되지 않을 수 있다. 참조 계수를 사용하는 일부 가비지 컬렉션 시스템(예: CPython)은 이 문제를 해결하기 위해 특정 순환 참조 감지 알고리즘을 사용한다. 또 다른 전략은 순환 참조를 생성하는 "백포인터"에 약한 참조를 사용하는 것이다. 참조 계수에서 약한 참조는 추적 가비지 수집기에서의 약한 참조와 유사하다. 이는 해당 객체의 존재가 참조된 객체의 참조 계수를 증가시키지 않는 특수한 참조 객체이다. 또한, 약한 참조는 참조된 객체가 가비지가 될 때, 해당 객체에 대한 약한 참조가 댕글링된 상태로 유지되는 것이 아니라, null 참조와 같은 예측 가능한 값으로 "만료"되므로 안전하다.
- 공간 오버헤드(참조 계수): 참조 계수는 각 객체의 참조 계수를 저장하기 위해 공간을 할당해야 한다. 계수는 객체의 메모리 옆에 저장하거나 다른 곳의 사이드 테이블에 저장할 수 있지만, 어느 경우든 참조 계수가 있는 모든 객체는 참조 계수를 위한 추가 저장 공간이 필요하다. 부호 없는 포인터 크기의 메모리 공간이 일반적으로 이 작업에 사용되므로 각 객체에 대해 32비트 또는 64비트의 참조 계수 저장 공간을 할당해야 한다. 일부 시스템에서는 태그된 포인터를 사용하여 객체 메모리의 사용하지 않는 영역에 참조 계수를 저장하여 이 오버헤드를 완화할 수 있다. 종종 아키텍처는 프로그램이 네이티브 포인터 크기에 저장할 수 있는 전체 메모리 주소 범위에 실제로 액세스하도록 허용하지 않는다. 주소의 특정 수의 상위 비트는 무시되거나 0이어야 한다. 객체가 특정 위치에 포인터를 안정적으로 가지고 있는 경우 참조 계수를 포인터의 사용하지 않는 비트에 저장할 수 있다. 예를 들어, Objective-C의 각 객체는 메모리의 시작 부분에 해당 클래스에 대한 포인터를 가지고 있다. iOS 7을 사용하는 ARM64 아키텍처에서 이 클래스 포인터의 19개의 사용하지 않는 비트가 객체의 참조 계수를 저장하는 데 사용된다.
- 속도 오버헤드(증가/감소): 단순한 구현에서는 참조 할당과 범위 밖으로 벗어나는 각 참조가 하나 이상의 참조 카운터를 수정해야 하는 경우가 많다. 그러나 외부 범위 변수에서 내부 범위 변수로 참조가 복사되는 일반적인 경우, 내부 변수의 수명이 외부 변수의 수명에 의해 제한되므로 참조 증가를 제거할 수 있다. 외부 변수가 참조를 "소유"한다. C++ 프로그래밍 언어에서는 이 기술이 `const` 참조를 사용하여 쉽게 구현되고 시연된다. C++의 참조 계수는 일반적으로 참조를 관리하는 "스마트 포인터"를 사용하여 구현된다. 스마트 포인터는 함수에 참조로 전달될 수 있으며, 이는 새로운 스마트 포인터를 복사 생성할 필요를 없애준다(이는 함수에 들어가면 참조 계수를 증가시키고 함수에서 나오면 감소시킨다). 대신 함수는 저렴하게 생성되는 스마트 포인터에 대한 참조를 받는다. Deutsch-Bobrow의 참조 계수 방법은 대부분의 참조 계수 업데이트가 실제로 지역 변수에 저장된 참조에 의해 생성된다는 사실을 이용한다. 이 방법은 이러한 참조를 무시하고 힙의 참조만 계산하지만, 참조 계수가 0인 객체를 삭제하기 전에 시스템은 스택과 레지스터를 스캔하여 해당 객체에 대한 다른 참조가 아직 존재하지 않는지 확인해야 한다. 페트랭크가 도입한 업데이트 통합을 통해 카운터 업데이트에 대한 오버헤드를 더욱 크게 줄일 수 있다. 주어진 실행 간격 동안 여러 번 업데이트되는 포인터를 생각해 보십시오. 먼저 객체 `O1`을 가리킨 다음 객체 `O2`를 가리키며, 간격이 끝날 때까지 객체 `On`을 가리킨다. 참조 계수 알고리즘은 일반적으로 `rc(O1)--`, `rc(O2)++`, `rc(O2)--`, `rc(O3)++`, `rc(O3)--`, ..., `rc(On)++`를 실행한다. 그러나 이러한 업데이트의 대부분은 중복된다. 간격이 끝날 때 참조 계수를 적절하게 평가하려면 `rc(O1)--` 및 `rc(On)++`를 수행하는 것으로 충분하다. 페트랭크는 일반적인 Java 벤치마크에서 카운터 업데이트의 99% 이상이 제거되는 것을 측정했다.
- 원자성 필요: 다중 스레드 환경에서 사용될 때, 이러한 수정(증가 및 감소)은 여러 스레드 간에 공유되거나 잠재적으로 공유되는 객체의 경우, 원자적 연산 (예: 비교 후 교환)이 필요할 수 있다. 원자적 연산은 멀티프로세서에서 비용이 많이 들고, 소프트웨어 알고리즘으로 에뮬레이션해야 하는 경우 더욱 비싸다. 스레드별 또는 CPU별 참조 계수를 추가하고 지역 참조 계수가 0이 되거나 더 이상 0이 아닐 때에만 전역 참조 계수에 액세스하거나 (또는, 대안으로 참조 계수의 이진 트리를 사용하거나, 아예 전역 참조 계수를 갖지 않는 대신 결정적 파괴를 포기) 이 문제를 피할 수 있지만, 이는 상당한 메모리 오버헤드를 추가하므로 특수한 경우에만 유용한 경향이 있다(예: Linux 커널 모듈의 참조 계수에 사용됨). 페트랭크의 업데이트 통합을 사용하면 쓰기 장벽에서 모든 원자적 연산을 제거할 수 있다. 카운터는 프로그램 실행 중에 프로그램 스레드에 의해 절대 업데이트되지 않는다. 단일 추가 스레드로 실행되는 수집기에 의해서만 수정되며 동기화가 없다. 이 방법은 병렬 프로그램에 대한 전체 정지 메커니즘으로 사용할 수 있으며, 동시 참조 계수 수집기와 함께 사용할 수도 있다.
- 실시간 아님: 참조 계수의 단순한 구현은 일반적으로 실시간 동작을 제공하지 않는다. 왜냐하면 모든 포인터 할당이 스레드가 다른 작업을 수행할 수 없는 동안 총 할당된 메모리 크기에 의해서만 제한되는 여러 객체를 재귀적으로 해제할 수 있기 때문이다. 추가 오버헤드를 감수하고, 참조되지 않은 객체의 해제를 다른 스레드에 위임하여 이 문제를 피할 수 있다.
5. 2. 1. 장점
쓰레기 수집의 장점은 다음과 같다.- 객체가 접근 불가능해지는 즉시 메모리가 해제되므로, 프로그래머가 객체의 해제 시점을 예측할 수 있다.
- 메모리 해제가 빠르게 이루어진다.
5. 2. 2. 단점
쓰레기 수집은 메모리 관리를 자동화하여 개발자의 부담을 줄여주지만, 몇 가지 단점도 존재한다.가장 큰 문제점 중 하나는 객체 간의 순환 참조 발생 시 메모리 누수가 발생할 수 있다는 것이다. 서로를 참조하는 객체들은 참조 횟수가 0이 되지 않기 때문에 쓰레기로 간주되지 않아 메모리에 계속 남아있게 된다.
또한 멀티스레드 환경에서는 참조 횟수 계산을 위해 원자적 명령을 사용해야 하므로 오버헤드가 발생할 수 있다. 여러 스레드가 동시에 객체의 참조 횟수를 변경하려고 할 때, 데이터의 일관성을 유지하기 위해 상호 배제 등의 기법이 필요하며, 이는 성능 저하로 이어질 수 있다.
참조 횟수가 0이 될 때, 해당 객체가 가리키는 다른 객체들 또한 동시에 해제될 수 있다. 이 과정에서 프로그램의 실행이 일시적으로 멈추는 현상이 발생할 수 있으며, 이는 실시간 시스템에는 부적합한 요소가 될 수 있다.
5. 3. 이스케이프 분석
이스케이프 분석은 컴파일 시점에 힙 할당을 스택 할당으로 변환하여 가비지 컬렉션의 부담을 줄이는 기술이다. 함수 내에서 할당된 객체가 함수 외부에서 접근 가능한지 분석하여, 함수 지역 할당이 다른 함수나 스레드에서 접근 가능하다면 "이스케이프"되었다고 판단한다. 이 경우 스택 할당이 불가능하다. 반대로, 객체가 함수 내에서만 사용된다면 스택에 직접 할당하여 함수가 반환될 때 해제함으로써 힙 메모리 관리 비용을 줄일 수 있다.6. 쓰레기 수집 방식에 따른 분류
쓰레기 수집 방식은 크게 프로그램 중단 여부에 따라 스톱-더-월드(Stop-the-World) 방식과 컨커런트(Concurrent) 방식으로 나뉜다.
스톱-더-월드 방식은 가비지 컬렉션을 수행하기 위해 프로그램 실행을 완전히 중단하는 방식이다. 이 방식은 구현이 간단하지만, 프로그램의 응답 시간을 지연시키는 단점이 있다.
컨커런트 방식은 프로그램 실행과 동시에 가비지 컬렉션을 수행하는 방식이다. 이 방식은 프로그램 응답 시간 지연을 최소화할 수 있지만, 구현이 복잡하다.
6. 1. 스톱-더-월드 방식
가비지 컬렉션을 위해 프로그램 실행을 완전히 멈추는 방식을 스톱-더-월드(Stop-the-world)라고 부른다. 이 방식은 구현이 간단하지만, 프로그램의 응답성을 크게 떨어뜨릴 수 있다. 가비지 컬렉션이 실행되는 동안 프로그램은 아무런 작업도 수행할 수 없기 때문이다. 따라서 실시간 시스템이나 사용자 인터랙션이 중요한 프로그램에서는 스톱-더-월드 방식의 가비지 컬렉션이 적합하지 않을 수 있다.6. 2. 컨커런트 방식
컨커런트 방식은 프로그램 실행과 동시에 가비지 컬렉션을 수행하는 방식을 말한다.7. 쓰레기 수집이 필요한 환경
쓰레기 수집은 자원 사용에 대한 엄격한 통제가 필요하므로 임베디드 시스템이나 실시간 시스템에서는 사용이 제한적이다. 하지만 여러 제한된 환경에 적용 가능한 쓰레기 수집기가 개발되어 왔다. .NET 마이크로 프레임워크와 .NET 나노프레임워크, 자바 플랫폼, 마이크로 에디션 등의 임베디드 소프트웨어 플랫폼은 대규모 플랫폼과 마찬가지로 쓰레기 수집 기능을 포함하고 있다.
7. 1. 제한된 환경
쓰레기 수집은 자원 사용에 대한 엄격한 제어가 필요하므로 임베디드 시스템이나 실시간 시스템에서는 잘 사용되지 않는다. 하지만 여러 제한된 환경과 호환되는 쓰레기 수집기가 개발되었다. 마이크로소프트의 .NET 마이크로 프레임워크, .NET 나노프레임워크 및 자바 플랫폼, 마이크로 에디션은 대규모 플랫폼과 마찬가지로 쓰레기 수집을 포함하는 임베디드 소프트웨어 플랫폼이다.8. 프로그래밍 언어별 지원 현황
다음은 "쓰레기 수집 (컴퓨터 과학)" 위키 페이지의 "프로그래밍 언어별 지원 현황" 섹션 내용이다.
많은 현대 프로그래밍 언어들이 쓰레기 수집 기법을 활용한다. C와 C++는 기본적으로 쓰레기 수집을 지원하지 않지만, Boehm garbage collector와 같은 라이브러리를 통해 해당 기능을 추가할 수 있다. 다만, 라이브러리 기반 방식은 객체 생성 및 소멸 메커니즘 변경이 필요하다는 단점이 있다.
ML, 하스켈, 얼랭, APL 등 대부분의 함수형 프로그래밍 언어는 가비지 컬렉션을 내장하고 있다. 특히 Lisp는 최초의 함수형 언어이자 쓰레기 수집을 도입한 최초의 언어로서 중요한 의미를 가진다.
자바, 스몰토크, 자바스크립트, ECMAScript 등 객체 지향 프로그래밍 언어들은 대부분 가비지 컬렉션을 기본적으로 제공한다. 자바 JDK에서는 G1, Parallel, CMS, Serial, C4, Shenandoah, ZGC 등의 다양한 가비지 컬렉터를 사용할 수 있다. 하지만 델파이와 C++는 쓰레기 수집을 기본적으로 지원하지 않고 소멸자를 통해 메모리를 관리한다.
루비, Julia와 같은 동적 타입 언어들도 가비지 컬렉션을 사용하며, 파이썬은 주로 참조 횟수 계산 방식을 사용하지만, 세대별 GC를 병행한다.[19][20] Perl 5나 5.3 이전 버전의 PHP는 참조 카운팅 방식만 사용했다. C#이나 Visual Basic .NET과 같은 .NET 언어는 .NET Framework/.NET Compact Framework/Mono/.NET Core 등 실행 환경에서 가비지 컬렉션을 이용할 수 있다.
Rust는 가비지 컬렉션 대신 소유권 개념을 통해 메모리를 관리하며,[14] C++처럼 소멸자를 정의하거나 참조 카운트 기반 스마트 포인터를 사용할 수 있다.
초창기 베이직 인터프리터에서도 가비지 컬렉션 기능이 제공되어, 문자열 연결 연산 후 불필요해진 영역을 재활용했다. 당시에는 가비지 컬렉션 실행 중 프로그램이 멈추는 현상이 발생하기도 했다.
Objective-C는 원래 참조 카운트 기반 객체 생명주기 관리 방식을 사용했으며 가비지 컬렉션 기능은 없었다. 하지만 애플(Apple Inc.)은 OS X 10.5부터 Objective-C 2.0에 자체 개발한 보수적인 세대별 GC 기반 런타임 수집기를 도입했다. 하지만 iOS에서는 애플리케이션 응답성 및 성능 문제로 인해 가비지 컬렉션이 도입되지 않았고, ARC (Automatic Reference Counting)를 사용한다. macOS에서도 `NSGarbageCollector`는 OS X 10.8부터 폐지 예정되었고, SDK 10.10을 마지막으로 폐지되었으며,[16] OS X 10.11을 마지막으로 해당 GC는 더 이상 탑재되지 않았으며,[17] macOS 10.12에서 완전히 폐지되었다. 2015년 5월 이후, Mac App Store에 신규 등록/갱신되는 앱은 GC를 사용할 수 없게 되었다.[18] 대안으로 ARC가 권장되고 있으며, Swift 또한 ARC를 채택하고 있다. 한편, GNUstep은 Boehm GC를 사용한다.
C++/CLI에서는 `gcnew`로 생성된 CLI 객체가 .NET Framework의 가비지 컬렉션에 의해 관리된다. C++/CX에서는 `ref new`로 생성된 Windows 런타임 객체가 COM 기반 참조 카운트에 의해 관리된다.
8. 1. 함수형 프로그래밍 언어
ML, Haskell, Erlang과 같은 대부분의 함수형 프로그래밍 언어는 자동 메모리 관리를 위해 쓰레기 수집을 사용한다.8. 2. 객체 지향 언어
대부분의 객체 지향 언어는 쓰레기 수집이 내장되어 있다. 대표적인 예로 자바, 스몰토크, 자바스크립트 등이 있다.자바 JDK에서 사용 가능한 가비지 컬렉터는 다음과 같다.
- G1
- Parallel
- 동시 마크 스윕 컬렉터 (CMS)
- Serial
- C4 (Continuously Concurrent Compacting Collector)
- Shenandoah
- ZGC
8. 3. 기타 언어
Objective-C는 전통적으로 가비지 컬렉션을 지원하지 않았지만, 2007년 OS X 10.5가 출시되면서 애플(Apple Inc.)은 자체 개발한 런타임 수집기를 사용하여 Objective-C 2.0에 대한 가비지 컬렉션을 도입했다. 그러나 2012년 OS X 10.8이 출시되면서 가비지 컬렉션은 LLVM의 자동 참조 계수 (ARC)로 대체되었고, ARC는 OS X 10.7과 함께 도입되었다. 또한 2015년 5월부터 애플은 App Store에서 새로운 OS X 애플리케이션에 가비지 컬렉션 사용을 금지했다. iOS의 경우, 애플리케이션 응답성과 성능 문제로 인해 가비지 컬렉션이 도입된 적이 없으며, 대신 iOS는 ARC를 사용한다.9. 관련 라이브러리 및 도구
C/C++에서는 다음과 같은 가비지 컬렉션 라이브러리와 스마트 포인터 도구를 사용할 수 있다.
C 언어에서 참조 계수 방식의 가비지 컬렉션을 이용하는 경우, 일반적으로 번거로운 코딩이 필요하지만, C++(C++)에서는 RAII를 활용한 스마트 포인터를 이용하여 이를 완화할 수 있다. 다음은 C++에서 사용 가능한 스마트 포인터의 예시이다.
- Boost C++ 라이브러리의 `boost::shared_ptr` 및 `boost::shared_array`. 참조 계수의 증가 및 감소 처리를 커스터마이징할 수 있는 `boost::intrusive_ptr`도 있다.
- C++11 이후의 `std::shared_ptr`
- Active Template Library의 `ATL::CComPtr` - COM 객체의 스마트 포인터이다.
- Windows 런타임의 `Microsoft::WRL::ComPtr` - Windows 런타임 객체의 스마트 포인터이며, COM 객체에도 사용 가능하다.
참조
[1]
웹사이트
What is garbage collection (GC) in programming?
https://www.techtarg[...]
2024-06-21
[2]
웹사이트
What is garbage collection (GC) in programming?
https://www.techtarg[...]
2022-10-17
[3]
웹사이트
What is garbage collection? Automated memory management for your programs
https://www.infoworl[...]
2023-02-03
[4]
웹사이트
A Guide to Garbage Collection in Programming
https://www.freecode[...]
2020-01-16
[5]
웹사이트
Garbage Collection - D Programming Language
https://dlang.org/sp[...]
2022-10-17
[6]
웹사이트
Garbage Collection
https://rebelsky.cs.[...]
2024-01-13
[7]
웹사이트
What is garbage collection? Automated memory management for your programs
https://www.infoworl[...]
2023-02-03
[8]
웹사이트
Fundamentals of garbage collection {{!}} Microsoft Learn
https://learn.micros[...]
2023-03-29
[9]
문서
Recursive functions of symbolic expressions and their computation by machine, Part I
http://portal.acm.or[...]
[10]
문서
RECURSIVE FUNCTIONS OF SYMBOLIC EXPRESSIONS AND THEIR COMPUTATION BY MACHINE (Part I) (12-May-1998)
http://www-formal.st[...]
[11]
간행물
分散記憶並列計算機における局所ごみ集めのスケジュール方式について
http://id.nii.ac.jp/[...]
情報処理学会
2000-05
[12]
간행물
Java-ASにおけるガベージコレクション対策に関する一考察
電子情報通信学会
2010-03
[13]
웹사이트
古典的Javaガベージコレクションを理解する
https://www.infoq.co[...]
2020-09-15
[14]
뉴스
メモリー管理を安全に、次代のシステムプログラミング言語「Rust」の魅力とは | 日経クロステック(xTECH)
https://xtech.nikkei[...]
[15]
웹사이트
メモリ管理を理解する(後編) (2/2):Cocoaの素、Objective-Cを知ろう(8) - @IT
https://www.atmarkit[...]
2019-02-14
[16]
웹사이트
NSGarbageCollector - Foundation {{!}} Apple Developer Documentation
https://developer.ap[...]
2019-02-14
[17]
웹사이트
Xcode Release Notes {{!}} Xcode 8.3
https://developer.ap[...]
2019-02-14
[18]
웹사이트
Apple Warns Developers Garbage Collection is Dead, Move to ARC – The Mac Observer
https://www.macobser[...]
2019-02-14
[19]
웹사이트
29.11. gc — ガベージコレクタインターフェース — Python 3.6.5 ドキュメント
https://docs.python.[...]
2019-02-10
[20]
웹사이트
Garbage Collection for Python
http://arctrix.com/n[...]
2019-02-10
[21]
웹인용
Recursive functions of symbolic expressions and their computation by machine, Part I
http://www-formal.st[...]
2012-11-13
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com