Article/OS - Deep Dive
메모리 압축(Memory Compaction), 그리고 Garbage Collector
조금씩 차근차근
2025. 4. 15. 02:23
우리는 단편화 문제에서 메모리 압축이 필요한 이유를 알 수 있었다. 지금부터 메모리 압축에 대해서 알아보자.
메모리 압축 방식
가장 기본적이고 간단한 메모리 압축은 두가지 방식으로 나뉘어진다.
Sliding
- 살아있는 객체들을 전부 한쪽 끝으로 몰아넣는다.
장점
- 추가 메모리의 요구가 없다.
단점
- 전체 메모리가 크게 이동해야 하기 때문에, stop-the-world 시간이 길어진다.
Copying Compaction
- 동작 방식
- 공간을 절반으로 나눈다.
- 1회 압축시마다, 데이터를 반대쪽으로 이동시킨다.
- 이를 압축마다 반복한다.
장점
- 구현이 단순하다.
- Bump Allocator 연산을 사용할 수 있기 때문에, Stop-the-world 시간이 빠르다.
- sizeof(object) 연산을 이용해 다음 빈 포인터를 O(1)만에 찾는다.
- 메모리의 이동 패턴이 단순해진다.
- 단점은 메모리 해제를 직접 할 수 없다는건데, 이건 GC가 해주면 된다.
- sizeof(object) 연산을 이용해 다음 빈 포인터를 O(1)만에 찾는다.
단점
- 사용할 수 있는 실제 공간이 줄어든다.
- 오래 살아남는 객체는 의미없이 복사되는 과정이 반복된다.
Stop-the-world는 왜 일어나는가?
손상된 힙
- GC가 객체의 참조관계를 파악해야 하는데,
- 애플리케이션 스레드가 계속해서 메모리를 할당/해제 하면
- 정확한 참조 관계를 파악할 수 없게 된다.
댕글링 포인터
- 기존에 참조하던 곳에 있던 데이터가 사라졌다!
- 사용하고 있던 도중에, compaction 한다고 내 메모리의 위치가 변경되면?
- 포인터가 갑자기 허공을 바라보게 된다.
- 이래서 필요한게, stop the world!
Stop-the-world의 문제점
- 모든 스레드가 멈춘다.
- 이는 실시간성이 중요한 서비스에는 큰 단점으로 다가온다.
해당 stop-the-world 시간을 줄이기 위해
- 나눠서 GC
- 메모리 컴팩션을 수행하는 영역을 최소화하고
- 메모리 컴팩션을 한번에 수행하는 것이 아닌, 여러 영역에 걸쳐 나눠서 수행하는 것이다.
- 세대 개념 도입
- Young Generation → Copying Compaction
- Old Generation → Sliding Compaction
- 어느정도 오래 살면, 굉장히 오래살더라~ 라는 이론을 도입한다.
GC는 왜 쓰는가?
- 개발자의 메모리 관리 생산성 향상
- 이때, 메모리를 지운 상태로만 그대로 두면, 메모리 fragmentation이 매우 심해진다.
- 그래서 우린, GC 시 메모리 컴팩션도 함께 수행하는 것이다.
간단한 Generational GC 동작 방식
- Eden 영역
- 신규로 할당할 힙 메모리는 Eden 영역을 사용한다.
- 해당 Eden영역은 스레드별로 별도로 할당되어 있어, 동시성 문제에 대하여 자유롭다.
- (동시에 여러 스레드가 같은 메모리주소에 할당하려고 하지 않는다.)
- 해당 Eden영역은 스레드별로 별도로 할당되어 있어, 동시성 문제에 대하여 자유롭다.
- eden 영역에서 한번 살아남은 객체는 Young Generation으로 이동한다.
- Copying Compaction 을 사용한다.
- 신규로 할당할 힙 메모리는 Eden 영역을 사용한다.
- Young Generation
- Copying Compaction 을 사용한다.
- Old Generation
- Sliding Compaction 을 사용한다.