가비지 컬렉션(GC) 개념 및 동작 원리
자바의 메모리 관리 방법인 가비지 컬렉션(GC)의 개념과 알고리즘, 동작과정을 알아보도록 하겠습니다.
GC란?
JVM(Java Virtual Machine)에서 자동으로 메모리를 관리하는 기법 중 하나로 프로그램이 더 이상 사용하지 않는 객체(가비지)를 식별하고 해당 객체가 차지하고 있는 메모리를 회수하여 새로운 객체를 할당할 수 있도록 합니다.
🤔 그럼 GC는 왜 필요할까요?
- C에서 메모리를 동적으로 할당받는 경우
char *s = malloc(sizeof(char) * 19);
strcpy(s, "Garbage Collection");
free(s); // 메모리 해제
C, C++의 경우 메모리를 관리하기 위해 코드 레벨에서 메모리를 동적으로 할당받고 해제해야 했습니다.
이렇게 메모리를 수동으로 관리하는 것은 번거로울 뿐만 아니라 할당받은 메모리 영역을 제대로 해제하지 않았을 경우 Memory Leak의 발생 가능성도 존재합니다.
따라서 GC는 개발자가 메모리 관리를 명시적으로 하지 않도록 해줌으로써 Memory Leak과 같은 에러들을 방지하기 위해 필요한 것입니다.
🤔 어떤 객체를 가비지로 식별하는 것일까요?
객체가 가비지인지 판별하기 위해서 reachability라는 개념을 사용합니다. Roots로부터 유효한 참조가 있으면 reachable Object, 없으면 unreachable Object로 구별하고 unreachable한 객체를 가비지로 간주합니다.
GC Root는
- Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
- 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
- Method Area에 저장된 static 변수에 대한 참조
에 해당되어 reachability를 판가름하는 기준이 되는 것입니다.
GC를 구현한 알고리즘
GC의 동작 과정을 알기 위해서는 먼저 GC를 구현한
- Mark And Sweep
- Compact
위 2가지의 알고리즘을 알아야 합니다.
[Mark And Sweep]
Mark And Sweep 알고리즘은 다음과 같이 진행됩니다.
GC Root로부터 모든 객체를 스캔하여 참조되지 않은 객체 즉, unreachable한 객체를 마킹합니다. 마킹 작업 후 마킹한 객체를 Heap에서 제거해 메모리에 여유 공간을 생성합니다.
[Compact]
Mark And Sweep 진행 후 메모리의 파편화를 막기 위해 Compact가 수행되며, 파편화를 막는 이유는 성능을 향상키기기 위함입니다. 이 과정은 알고리즘의 종류에 따라 수행 여부가 달라집니다.
GC의 Heap영역
JVM의 Heap 영역은 Young Generation, Old Generation으로 나뉘며 각각의 영역에서 발생하는 GC를 minor gc/major gc로 정의합니다.
Young Generation 영역은 Eden, Survival 0, Survival 1로 이루어진 영역으로 각 영역은 다음과 같이 정리할 수 있습니다.
- Eden: 새롭게 생성된 객체들이 할당되는 영역
- Survival: minor gc로부터 살아남은 객체들이 존재하는 영역으로 Survival 0, Survival 1 영역 중 하나는 비어있어야 하는 규칙이 있습니다.
Old Generation 영역은 Young Generation에서 오랫동안 살아남은 객체들이 존재하는 영역입니다.
GC의 동작과정
이제 GC의 동작과정을 확인해보겠습니다.
먼저 새롭게 생성된 객체들이 Eden 영역에 할당됩니다. 객체에 있는 숫자는 age bit로, 객체가 GC를 통해 몇 번 살아남았는지 추적할 수 있는 정보입니다.
Eden 영역이 꽉 차서 객체가 더 이상 할당될 수 없을 때 minor gc가 발생하게 되고, Mark And Sweep이 진행하게 됩니다. 앞서 설명한 대로 reachable한 객체와 unreachable한 객체를 마킹하고(Mark) 마킹된 객체는 제거하는 과정(Sweep)입니다.
이 과정에서 살아남은 객체들은 Survival 영역으로 이동하게 되고 age bit 또한 증가하게 됩니다.
다시 Eden영역이 꽉 차게 되면 minor gc가 발생하고 Mark And Sweep을 통해 살아남은 객체가 다른 Survival 영역으로 이동하게 됩니다. 마찬가지로 age bit도 증가하게 됩니다.
과정이 반복되어 객체의 age bit가 특정 임계점에 도달하면 Old Generation영역으로 이동하게 됩니다. 이 과정을 Promotion이라고 합니다. 추가적으로 age bit는 GC 알고리즘에 따라 다르게 설정되는 값으로 고정적이지는 않습니다.
Old Generation 영역도 꽉차게 되면 이때 major gc가 발생하게 됩니다.
🤔 그럼 왜 Heap의 메모리 구조를 Young Generation과 Old Generation으로 나누어 관리할까요?
Oracle GC 튜닝가이드에 따르면 대부분의 객체는 수명이 짧다고 합니다. 따라서 효율적인 메모리 운영을 위해서 두 영역을 나누어 관리하는 것입니다.
여기까지의 내용만 보면 GC는 장점만 존재하는 것 같지만, 물론 단점도 존재합니다.
STW(Stop-The-World)
STW(Stop-The-World)는 GC 실행을 위해 JVM이 애플리케이션이 실행을 멈추는 것을 의미합니다. 여기서 중요한 점은 GC를 실행하는 스레드를 제외한 나머지 스레드도 모두 작업을 멈추고, GC 작업을 완료한 이후에 중단했던 작업을 다시 시작한다는 점입니다.
STW가 해당 시점에 발생하는 이유는 객체의 정확성과 일관성을 보장하기 위함이라고 할 수 있습니다. GC 종류에 따라 STW가 발생하는 시간이 다르기 때문에 GC 튜닝은 상황에 맞는 GC를 선택하여 STW의 시간을 최소화하는 것이 목표입니다.
💭 GC를 알아야 하는 이유
GC는 자동으로 메모리를 관리하는 기법으로 애플리케이션 성능에 직접적인 영향을 미칩니다. 이러한 과정을 모르고 성능을 위해 GC 튜닝을 하는 경우 오히려 성능 저하를 유발할 수 있습니다.
따라서 GC 동작 원리와 관련 옵션을 잘 이해하면 애플리케이션 메모리 사용 패턴에 맞게 최적화할 수 있기 때문에 GC를 알아야 하는 것입니다.
GC를 구현한 알고리즘이 더 최신이라고 해서 반드시 내 애플리케이션에서 해당 GC 방식이 더 적절하다고 보증할 수 없습니다. 따라서 GC 상태를 모니터링하고 애플리케이션 성격에 알맞은 GC 방식과 메모리 크기를 설정해 보고 좋다면 적용하는 step을 거쳐야 합니다.
참고
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
Java Garbage Collection Basics
Java Overview Java is a programming language and computing platform first released by Sun Microsystems in 1995. It is the underlying technology that powers Java programs including utilities, games, and business applications. Java runs on more than 850 mill
www.oracle.com
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html
Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide, Release 8
docs.oracle.com
https://product.kyobobook.co.kr/detail/S000001032977
자바 성능 튜닝 이야기 | 이상민 - 교보문고
자바 성능 튜닝 이야기 | 『자바 성능 튜닝 이야기』는 고성능 애플리케이션을 위해 고려 해야 할 복잡한 요소들을 하나하나 짚어 주는 책이다. 장애를 일으키는 반복적인 코딩 이슈부터 시스템
product.kyobobook.co.kr