자동 메모리 관리 (가비지 컬렉션, 참조 카운팅)
태그: C++, Cpp, Garbage Collection, GC, memory, RefCounting
카테고리: Cpp
🤯 언리얼을 하기 위해 C++ 기억 되살리기 프로젝트
- 자동 메모리 관리
std::shared_ptr
을 배우기 전에, 자동 메모리 관리에 대해 알아보자~- 주로 쓰는 두 가지 기법
- 가비지 컬렉션 (GC, Garbage Collection) : java와 C#에서 지원
- 참조 카운팅 (RefCounting) : Swift와 애플 objective-C 에서 지원
가비지 컬렉션 (Garbage Collection)
- 메모리 누수를 막으려는 시도
- 주기적으로 컬렉션 실행
- 충분한 여유 메모리가 없을 때, 컬렉션이 실행 됨
- 스케쥴에 따라 수동으로도 실행 가능
- 매 주기마다, GC는 루트(root)를 확인함
- 전역 변수
- 스택
- 레지스터
- 힙에 있는 개체에 루트를 통해 접근할 수 있는지 판단
- 접근할 수 없다면, 가비지로 판단해 메모리 해제
작동원리 (단순화)
스택이 루트.
스택의 오브젝트를 하나씩 훑음. 이 오브젝트가 다른 오브젝트 멤버를 가지고 있는지 등 체크.
루트로 접근할 수 없는 개체 -> 아무도 참조하고 있지 않다는 것.
컬렉팅하여 삭제.
- 모든 개체를 훑는데에 시간이 걸릴텐데 어떻게 최적화 되어 있는가? 혹은 어떻게 최적화 할텐가!
- C# 에서 구현된 게 있음.
세대별 가비지 컬렉션
힙영역에 할당된 메모리들은 0세대, 1세대, 2세대 세가지로 구분된다.
가장 처음 할당되는 메모리들은 0세대이며 가비지 컬렉션이 한번이 이루어 질때마다 한세대씩 증가한다.가비지 컬렉션은 0세대 가비지 컬렉션 부터 발생하며,
0세대 가비지 컬렉션은 0세대 메모리들만을 메모리해제 한다. 남은 메모리들은 1세대씩 증가한다.
0세대 가비지 컬렉션이 이루어 졌는데도 메모리가 부족하다 판단되면 1세대 가비지 컬렉션이 발생한다.
그러면 0세대 메모리와 1세대 메모리 모두 사용하지 않는 메모리는 해제된다. 그리고 한세대씩 증가하게 된다.
0세대와 1세대 가비지 컬렉션이 발생하였는데도 메모리가 부족하면 2세대 가비지 컬렉션이 발생한다.
- C# 에서 구현된 게 있음.
문제점
- 사용되지 않는 메모리를 즉시 정리하지 않음
- GC가 메모리를 해제해야 할지 판단하는 동안 프로그램이 멈추거나 버벅거릴 수 있음
참조 카운팅 (RefCounting)
가비지 컬렉션보다 실시간 프로그램에서 좀 더 나은 방법
- 가비지 컬렉션처럼, 개체에 대한 참조가 없을 때 개체가 해제됨
- 내 개체를 누가 안참조해, 안가지고 있어, 안 쓰고 있어 -> 바로 해제
- 언제든 참조 횟수를 활용해서 특정 개체가 몇 번이나 참조되고 있는지 판단 가능
- 어떤 개체 A를 다른 개체 B가 참조할 때 횟수가 늘어남
- B가 참조를 그만둘 때 횟수가 줄어듦
- B가 범위(Scope) 를 벗어나는 경우
작동 원리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class StringValue
{
private:
int mRefCount; // 참조 카운트 확인용
// (생성할 때 0으로 초기화 한다고 가정)
char* mString;
};
class MyString
{
private:
StringValue* mStringValue;
};
MyString cat("Coco"); // 스택에 cat 오브젝트 생김
// 오브젝트엔 mStringValue가 있고
// 참조 카운트 1
MyString anotherCat("Lulu"); // 스택에 anotherCat 오브젝트 생김
// 참조 카운트 1
anotherCat = cat; // anotherCat에 있던게 사라지고 cat이 들어가는 것
// cat은 그럼 이제 2번 참조가 되는 오브젝트라는 것..
// cat의 MyString 참조 카운트 2가 됨
강한(strong) 참조
- 강한 참조란, 개체 A가 개체 B를 참조할 때, 개체 B는 절대 소멸되지 않음을 의미
- 강한 참조의 수를 저장하기 위해 강한 참조 카운트를 사용
- 일반적으로 새 인스턴스, 즉 개체에 대한 참조를 만들 때 강한 참조 횟수가 늘어남
- 강한 참조 횟수가 0이 될 때 해당 개체는 소멸됨
shared_ptr
은 강함 참조, 약한 참조의 개념 둘 다 갖고 있음
문제점
- 참조 횟수는 너무 자주 바뀜
- 멀티 스레드 환경에서 안전하려면,
lock
이나원자적(atomic) 연산
이 필요 - ++mRefCount보다 확연히 느림
- 멀티 스레드 환경에서 안전하려면,
- 순환(circular) 참조 발생하면…
- 개체 A가 B를 참조
- 개체 B가 A를 참조
- 이럴 경우 절대 해제되지 않음! -> C++에 해결책이 있음
GC나 RefCount를 쓰면 메모리 누수가 없을까?
- 전통적인 메모리 누수는 없음
- delete를 잊었다거나
- 하지만 여전히 발생할 수 있음
- 위에서 언급한 순환 참조…
댓글남기기