가비지 컬렉션 (Garbase Collection, GC)
- 메모리 관리 방법
- JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당됐던 메모리 중 필요 없게 된 메모리 객체(garbage)를 모아 주기적으로 제거하는 프로세스
- 프로그램을 개발하다 보면 유효하지 않은 메모리인 Garbage 가 발생하게 되는데, *메모리 누수를 방지하기 위해서 이러한 불필요한 메모리를 제거해야 한다.
- C / C++ 언어에서는 가비지 컬렉션이 없어 free() 라는 함수를 통해 직접 메모리를 해제해주어야 한다.
- Java 나 Kotlin 을 이용하면 가비지 컬렉션가 주기적으로 검사하여 메모리를 청소해 준다.
- 하지만 자동으로 처리해 준다 해도 메모리가 언제 해제되는지 정확하게 알 수 없어 제어하기 힘들고, 가비지 컬렉션이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생되는 문제점이 있다.
- 메모리 누수(Memory Leak) : 프로그램이 동적으로 할당한 메모리 영역 중 일부를 더 이상 사용하지 않음에도 불구하고 해제하지 않아, 사용할 수 있는 메모리가 점점 줄어드는 현상
JVM Heap 영역
- 처음 설계될 때 다음의 2가지를 전제(Weak Generational Hypothesis)로 설계되었다.
- 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
- 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
- 즉, 객체는 대부분 일회성 되고, 메모리에 오랫동안 남아있는 경우는 드물다.
- 그래서 객체의 생존 기간에 따라 물리적인 Heap 영역을 Young, Old 총 2가지 영역으로 나누어 설계되었다.
Young 영역
- 새롭게 생성된 객체가 할당(Allocation)되는 영역
- 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
- Young 영역에 대한 가비지 컬렉션을 Minor GC 라고 부른다.
Old 영역
- Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
- Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
- Old 영역이 Young 영역보다 크게 할당되는 이유는 Young 영역의 수명이 짧은 객체들은 큰 공간을 필요로 하지 않고, 큰 객체들은 Young 영역이 아니라 바로 Old 영역에 할당되기 때문이다.
- Old 영역에 대한 가비지 컬렉션을 Major GC 라고 부른다.
Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우
- Young 영역에서 가비지 컬렉션(Minor GC)이 실행될 때 모든 Old 영역에 존재하는 객체를 검사하여 참조되지 않는 Young 영역의 객체를 식별하는 것이 비효율적이다.
- 이러한 경우를 대비하여 Old 영역에는 512 bytes 의 덩어리(Chunk)로 되어 있는 카드 테이블(Card Table)이 존재한다.
- 카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 그에 대한 정보가 표시된다.
- Young 영역에서 가비지 컬렉션이 진행될 때 카드 테이블만 조회하여 GC 의 대상인지 식별할 수 있다.
가비지 컬렉션의 동작 방식
- Young 영역과 Old 영역은 서로 다른 메모리 구조로 되어 있기 때문에, 세부적인 동작 방식은 다르다.
- 하지만 기본적으로 가비지 컬렉션이 실행된다고 하면 다음의 2가지 공통적인 단계를 따르게 된다.
1. STW (Stop The World)
- 가비지 컬렉션을 실행하기 위해 JVM 이 애플리케이션의 실행을 멈추는 작업
- GC 가 실행될 때는 GC 를 실행하는 스레드를 제외한 모든 스레드들의 작업이 중단되고, GC 가 완료되면 작업이 재개된다.
- 당연히 모든 스레드들의 작업이 중단되면 애플리케이션이 멈추기 때문에, GC 의 성능 개선을 위해 튜닝을 한다고 하면 보통 stop-the-world 의 시간을 줄이는 작업을 하는 것이다.
- 또한 JVM 에서도 이러한 문제를 해결하기 위해 다양한 실행 옵션을 제공하고 있다.
2. Mark and Sweep
- Stop The World 를 통해 모든 작업을 중단시키면, GC 는 스택의 모든 변수 또는 Reachable 객체를 스캔하면서 각각이 어떤 객체를 참고하고 있는지를 탐색하게 된다. 그리고 사용되고 있는 메모리를 식별하는데, 이러한 과정을 Mark 라고 한다.
- 이후에 Mark 가 되지 않은 객체들을 메모리에서 제거하는데, 이러한 과정을 Sweep 라고 한다.
- Sweep 후에 분산된 객체들을 Heap 의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축하는데, 이러한 과정을 Compact 라고 한다. (가비지 컬렉터 종류에 따라 하지 않는 경우도 있음)
- Mark : 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업
- Sweep : Mark 단계에서 사용되지 않음으로 식별된 메모리를 해제하는 작업
- Compact : 해제된 메모리 영역을 압축하여 힙의 시작 주소로 모으는 작업
Minor GC 의 동작 방식
- Minor GC 를 정확히 이해하기 위해서는 Young 영역의 구조에 대해 이해를 해야 한다. Young 영역은 1개의 *Eden 영역과 2개의 *Survivor 영역, 총 3가지로 나뉜다.
- Eden 영역 : 새로 생성된 객체가 할당(Allocation)되는 영역
- Survivor 영역 : 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역
- 객체가 새롭게 생성되면 Young 영역 중에서도 Eden 영역에 할당(Allocation)이 된다.
- 그리고 Eden 영역이 꽉 차면 Minor GC 가 발생하게 되는데, 사용되지 않는 메모리는 해제되고 Eden 영역에 존재하는 객체는 (사용 중인) Survivor 영역으로 옮겨지게 된다.
- Survivor 영역은 총 2개이지만 반드시 1개의 영역에만 데이터가 존재해야 한다.
Young 영역의 동작 순서
- 새로 생성된 객체가 Eden 영역에 할당된다.
- 객체가 계속 생성되어 Eden 영역이 꽉 차게 되고 Minor GC 가 실행된다.
- Eden 영역에서 사용되지 않는 객체의 메모리가 해제된다.
- Eden 영역에서 살아남은 객체는 1개의 Survivor 영역으로 이동된다.
- 1~2번의 과정이 반복되다가 Survivor 영역이 가득 차게 되면 Survivor 영역의 살아남은 객체를 다른 Survivor 영역으로 이동시킨다(1개의 Survivor 영역은 반드시 빈 상태가 된다).
- 이러한 과정을 반복하여 계속해서 살아남은 객체는 Old 영역으로 Promotion(이동)된다.
- 객체의 생존 횟수를 카운트하기 위해 Minor GC 에서 객체가 살아남은 횟수를 의미하는 age 를 Object Header 에 기록한다. 그리고 Minor GC 때 Object Header 에 기록된 age 를 보고 Promotion 여부를 결정한다.
- Survivor 영역 중 1개는 반드시 사용이 되어야 한다. 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 모두 사용량이 0 이라면 현재 시스템이 정상적인 상황이 아님을 파악할 수 있다.
HotSpot JVM
- Eden 영역에 객체를 빠르게 할당(Allocation) 하기 위해 bump the pointer 와 TLABs 라는 기술을 사용하고 있다.
bump the pointer
- Eden 영역에 마지막으로 할당된 객체의 주소를 캐싱해 두는 것
- 새로운 객체를 위해 유효한 메모리를 탐색할 필요 없이 마지막 주소의 다음 주소를 사용하게 함으로써 속도를 높인다.
- 새로운 객체를 할당할 때 객체의 크기가 Eden 영역에 적합한지만 판별하면 되므로 빠르게 메모리 할당을 할 수 있다.
- 싱글 스레드 환경이라면 문제가 없겠지만 멀티스레드 환경이라면 객체를 Eden 영역에 할당할 때 락(Lock)을 걸어 동기화를 해주어야 한다.
TLABs (Thread-Local Allocation Buffers)
- 멀티 쓰레드 환경에서의 성능 문제를 해결하기 위해 도입하게 되었다.
- 각각의 스레드마다 Eden 영역에 객체를 할당하기 위한 주소를 부여함으로써 동기화 작업 없이 빠르게 메모리를 할당하도록 하는 기술
- 각각의 스레드는 자신이 갖는 주소에만 객체를 할당함으로써 동기화 없이 bump the pointer 를 통해 빠르게 객체를 할당하도록 하고 있다.
Major GC 의 동작 방식
- Young 영역에서 오래 살아남은 객체는 Old 영역으로 Promotion 되고, 객체들이 계속해서 승격되어 Old 영역의 메모리가 부족해지면 Major GC 가 발생하게 된다.
- Young 영역은 일반적으로 Old 영역보다 크기가 작기 때문에 GC 가 보통 0.5초에서 1초 사이에 끝난다. 그렇기 때문에 Minor GC 는 애플리케이션에 크게 영향을 주지 않는다.
- 하지만 Old 영역은 Young 영역보다 크며 Young 영역을 참조할 수도 있다. 그렇기 때문에 Major GC 는 일반적으로 Minor GC 보다 시간이 오래 걸리며, 10배 이상의 시간을 사용한다.
'안드로이드 > etc.' 카테고리의 다른 글
[CS] CPU, 주기억장치(ROM, RAM(SRAM, DRAM), 보조기억장치(HDD, SDD) (0) | 2024.08.28 |
---|---|
[CS] 가상 메모리 (Virtual Memory) (0) | 2024.08.23 |
[Image] 래스터(JPG, PNG, BMP, WebP), 벡터(SVG) 이미지 (0) | 2024.08.19 |