1) 리소스
: 운영체제로부터 빌려와 사용한 후 반환해야하는 것
2) 메모리도 리소스
→ 동적 할당된 메모리를 사용한 후에는 시스템에 돌려줘야 함
1) 메모리 누수 (Memory Leak)
: 메모리 사용이 끝났음에도 불구하고 해제하지 않은 것
2) 이중 해제 (Double Free)
: 이미 해제된 메모리임에도 불구하고 다시 해제하는 것
+) 댕글린 포인터/레퍼런스 (Dangling Pointer/Reference)
: 이미 해제된 메모리를 가리키는 포인터/레퍼런스
3) 섣부른 해제 (Premature Free)
: 아직 사용이 끝나지 않았음에도 불구하고 해제하는 것
→ 자동 메모리 관리(automatic memory management)
: 위와 같은 불편함을 해결하기 위한 기술로, 가비지 컬렉션이 이에 해당함
1) 가비지 컬렉터(Garbage Colletor)가 더이상 사용하지 않는 메모리를 재사용하는 것
2) 종류
1) 도달 가능성(Reachability)으로 생존을 가정
→ 루트(Root)에서 출발해 해당 메모리까지 도달할 수 없는 경우 가비지로 가정
2) 루트의 종류
: 스택 루트, CPU 레지스터, 정적 필드 (static) 등
1) 해당 메모리에 참조하는 것이 없을 때 가비지로 가정
2) 서로 다른 두 메모리가 서로를 참조하는 순환 참조를 주의해야 함
→ 실제로 살아있지 않지만, 살아있다고 판단할 수 있음
→ 참조 횟수에 영향을 주지 않는 약한 참조를 사용할 수 있음
1) 매니지드 언어 (Managed Language)
: 가비지 컬렉션을 지원하는 언어 (C# 포함)
→ 언매니지드 언어(Unmanaged Language)는 매니지드 언어의 반대 개념으로, 네이티브 언어(Native Language)라고도 함
2) C#의 가비지 컬렉션
1) 매니지드 힙 (Managed Heap)
: 가비지 컬렉터가 관리하는 메모리
2) 매니지드 힙은 0세대, 1세대, 2세대 총 3세대로 나누어 관리됨
3) 세대를 나눈 이유는 메모리를 재사용하기 용이하기 때문
1) C#의 모든 참조 타입 객체는 매니지드 힙의 0세대에 할당, 연속적 배치
2) 매니지드 힙은 메모리를 미리 시스템으로부터 할당 받아 놓기 때문에, 스택에서 메모리를 할당하는 속도만큼 빠르게 할당 및 접근할 수 있음
3) 85KB 이상의 큰 객체는 LOH (Large Object Heap)라는 2세대 메모리에 할당
1) 추적(Tracing) 방식 사용
2) 가비지 컬렉터가 가장 적합한 때에 자동으로 메모리를 해제
3) 실제로 시스템에 메모리를 돌려주는 것은 아니며, 다른 메모리에 의해 덮여쓰여짐
→ LOH에서는 덮어쓰는 연산이 비싸기 때문에 덮어쓰기를 생략
→ 따라서 단편화 발생할 수 있음
4) 참조 변수의 주소값을 모두 수정하며, 각 세대의 시작을 가리키는 포인터 또한 수정
→ 덮어쓰는 과정에서 뒤쪽에 할당된 데이터가 앞으로 당겨지는데, 이 과정을 통해 메모리 단편화가 발생한 부분을 없앰
→ 데이터가 앞으로 당겨지면 메모리 주소(포인터)도 변경
5) 가비지 컬렉션 발생 순서
가비지 컬렉션은 자원을 적게 소모하는 연산이 아니며, 멀티 스레드 환경에서는 가비지 컬렉션이 수행되는 동안 다른 스레드가 중단(Suspended)되기 때문에 가비지 컬렉션과 성능의 관계를 이해하고 있어야 함
1) 참조 카운팅 방식으로 가비지 수집이 일어나지 않음
2) 필요하다면 약한 참조를 사용할 수 있음
3) 빈번한 할당을 조심해야 함
4) 너무 큰 객체 할당을 피하자
5) 복잡한 참조 관계를 피하자
6) 관리되지 않는 리소스도 있음