Garbage Collector (GC)
메모리 관리는 언제나 중요합니다. 할당된 메모리보다 큰 메모리 리소스를 사용하려 하거나, 메모리에서 해제된 객체 등에 재접근하는 경우 등 메모리와 관련된 다양한 개발자의 실수로 프로그램이 뻗어버리는 일이 생기곤 하기 때문입니다. JVM 의 GC 는 Garbage Collecting Algorithm 을 운용하여 개발자가 비즈니스 로직에만 집중할 수 있도록 도와줍니다.
GC 란?
GC 는 JVM 메모리 구조 속 Heap Area 에 있는 인스턴스들을 대상으로 작동하며, 더 이상 참조되지 않는 인스턴스의 메모리를 자동으로 반환하도록 하는 메모리 관리 기법입니다. Heap 에는 참조형 타입의 인스턴스가 할당됩니다.
장, 단점은 다음과 같습니다.
장점
- 개발자가 메모리 할당 및 해제에 관여하지 않아도 된다.
- 이미 해제된 메모리에 접근하거나, 중복 해제 등의 버그를 줄일 수 있다.
- 메모리 누수가 발생할 확률이 감소한다.
단점
- 해제할 메모리를 결정하는데 사용되는 알고리즘에 의해 비용이 든다.
- GC 가 언제 작동될지 예측하기 어렵다.
GC 의 구조
JVM 의 Heap Area 는 Eden, Survivor 1, Survivor 2, Old, Permanent 영역으로 구분됩니다.
- 객체가 생성되어 Eden 영역에 메모리를 할당
- 이후 Eden 영역이 가득차면 Minor GC 가 실행
- 할당이 해제되지 않은 객체들을 Survivor 1 영역으로 이동
- Survivor 1 영역이 가득 차면 Minor GC 가 실행되고, 여기서도 살아남는 인스턴스는 Survivor 2 영역으로 이동
- Survivor 1 과 Survivor 2, 둘 중 하나는 반드시 비어 있어야 한다. 만약 두 영역에 모두 데이터가 존재하거나 둘 다 사용량이 0 인 경우는 비정상적인 경우
- Eden 영역에 적재되어 GC 대상이 되었지만 Survivor 영역보다 메모리가 큰 인스턴스, 또는 Survivor 2 에서도 살아남은 인스턴스는 Old 영역으로 이동
- Old 영역이 가득 차면 Major GC 실행
- Heap 영역이 가득 차면 Full GC (Minor GC + Major GC) 가 실행
대표적인 GC 알고리즘
Serial GC 는 순차적인 GC 라는 의미로, Mark-Sweep-Compact 알고리즘이 한 번에 하나 씩만 동작합니다.
Stop-the-world 시간이 너무 길기 때문에, 사용하지 않습니다.
Java 8 기준, Parallel GC 를 기본 GC 알고리즘으로 채택하고 있습니다. 해당 알고리즘은 여러 개의 스레드로 Mark-Sweep-Compact 을 수행합니다. 이로 인해 Stop-the-world 시간이 줄어들게 됩니다.
Parallel Old GC 는 Parallel GC 와 비교했을 때, Old 영역이 처리되는 방식에 차이점이 있습니다. Mark-Sweep-Compact 알고리즘이 아닌 Mark-Summary-Compact 알고리즘을 채택하여 운용됩니다.
Mark-Sweep-Compact 와 Mark-Summary-Compact
Mark-Sweep-Compact 는 살아있는 객체를 식별(Marking), Old 영역의 가장 앞 부분부터 살아있는 것만 남기고 삭제(Sweeping)하며, 마지막으로 남은 객체들을 앞쪽으로 모아(Compacting)줍니다.
Mark-Summary-Compact 는 Mark-Sweep-Compact 와 작동 방식은 같으나, 병렬적으로 처리됨에 의의가 있습니다.
G1GC 는 Garbage First Garbage Collecting 입니다.
현존하는 GC 중 Stop-the-world 시간이 가장 짧으며, Heap 영역을 Region 단위로 구분하고 각각의 Region 에서 GC 가 수행됩니다. GC 수행 이후, 살아남은 인스턴스에 대해 Compact 작업을 수행하여 Heap 앞쪽으로 모아줍니다.
In Android
런타임 실행 환경으로 Dalvik VM 이 아닌 ART(Android Run Time) 를 채택한 Android 5.0(롤리팝) 부터는 어떠한 GC 알고리즘이 사용될까요?
CMS (Concurrent Mark-Sweep)
Stop-the-world 현상으로 애플리케이션이 멈추는 상황을 최대한 피하기 위해 만들어졌습니다. Reachable 한 객체를 찾는 행위를 총 4단계로 나누어 수행합니다.
- Initial Mark - GC Root 가 참조하는 객체만 마킹 (Stop-the-world 발생)
- Concurrent Mark - 참조하는 객체를 따라가며 지속적으로 마킹 (Stop-the-world 발생하지 않음)
- Remark - Concurrent Mark 과정에서 변경된 사항이 없는지 다시 한 번 마킹하며 확정하는 과정 (Stop-the-world 발생)
- Concurrent Sweep - 접근할 수 없는 객체를 제거하는 과정 (Stop-the-world 발생하지 않음)
이후, Android 8.0(오레오) 부터는 새로운 GC 알고리즘이 적용됩니다.
CC (Concurrent Copying)
CC 알고리즘에는 RegionTLAB(Thread Local Alloction Buffers) 이라는 범프 포인터 할당자를 사용할 수 있습니다. TLAB 은 각 스레드마다 따로 할당을 받게 되는데, 프로세스 메모리의 Heap 영역 속 Eden 영역에 자리하고 있습니다. 객체가 새로이 생성이 되면 Eden 에 할당되지 않고 TLAB 에 할당됩니다. TLAB 은 각 스레드의 고유 공간이므로 접근이 빠릅니다. 이로 인해 기존의 Eden 영역에서 GC 를 수행하는 것보다 훨씬 빠르게 Young 하지만 Unrecheable 한 인스턴스들로부터 메모리를 반환받을 수 있습니다.
정말 흔한 포스팅 주제이고, 이와 관련한 정말 많은 포스트를 만나왔습니다만, 도무지 머릿 속에 들어오질 않아 결국 직접 정리하게 됐습니다. 확실히 직접 정리하는 편이 그냥 보는 것보다 백 번 나은 것 같습니다.
'Android > Tech' 카테고리의 다른 글
[Jetpack Compose] Glide 와 Coil, 무엇을 사용하면 좋을까. (2) | 2023.03.07 |
---|---|
안드로이드와 Clean Architecture (0) | 2023.02.11 |
Jetpack Compose 의 remember (0) | 2023.01.19 |
Serializable, Parcelable (0) | 2023.01.12 |
SharedPreferences 가 DataStore 에 대체될 수 밖에 없었던 이유 (0) | 2022.12.27 |