주제
목차
- gc 설명
- 동작원리
- 종류
내용
GC 가 무엇인지
더이상 사용하지 않는 객체 등을 메모리에서 해제(삭제)하는 JVM의 작업Java 프로세스가 동작하는 과정에서 GC는 불필요한 또는 더이상은 사용하지 않는 객체들을 메모리에서 제거함으로써, Java 프로세스가 한정된 메모리를 효율적으로 사용할 수 있게 해준다.
GC의 장점 및 단점
- 장점
- 개발자의 실수로 인한 치명적인 프로세스 오작동을 예방할 수 있다.
- 개발자가 메모리를 크게 신경 안써도 된다(코드를 짤때 메모리 효율도 생각하긴 해야함)
- 단점
- 언제 일어날지 모른다
추가자료
그리고, GC는 weak generational hypothesis 아래 내용에 의해서 도입되었다.
① 대부분의 객체는 금방 unreachable이 된다.
: 아래 for문 안에서 생성된 100개의 Obj는 더이상 사용되지 않을 것.
② 오래된 객체에서 생성된지 얼마안된 객체로의 참조는 적다. - POJO객체(Plain Old Java Object)
: 보통 어떤 값이나 상태를 저장하기 위해 POJO 객체를 생성하고, 다른 메소드나 클래스에 전달하고, 다 사용한 객체는 더이상 사용하지 않는다.
heap 구조 [gc 출몰지역]
heap 영역에서 어떤 방식으로 GC가 발생하는지 heap 구조를 통해 세부적으로 알아봅시다.
heap 영역은 크게 Young Generation과 Old Generation으로 나뉩니다.

- Young Generation은 새로운 객체들이 할당되는 영역이고 Old Generation은 Young Generation에서 오랫동안 살아남은 객체들이 존재하는 영역입니다.
- Young Generation은 Eden, Survivor 0, Survivor 1로 나누어집니다.
- 이러한 heap 구조에서 GC가 언제 일어날까요?
GC의 수거 대상: Reachability
즉, Unreachable Objects는 참조되지 않은 객체이며 Reachable Objects는 참조된 객체라고 볼 수 있습니다.

사진과 같이 GC 루트에서부터 각각 참조하고 있는 객체들을 하나씩 하나씩 탐색해나갑니다.
그러면 참조된 객체들은 Reachable Objects, 참조되고 있지 않은 객체들을 Unreachable Objects 하다고 표현합니다. 이러한 Unreachable Objects가 GC의 수거 대상이 됩니다.
그렇다면 GC 루트가 될 수 있는 조건은 무엇?
- JVM stack 영역의 지역변수나 파라미터들, 메소드 영역에 있는 static 데이터들, 자바 네이티브 인터페이스에 의해 생성된 객체들이 해당됩니다.
GC가 왜 있어야 하는지
- GC가 있어야 효율적으로 메모리를 관리할 수 있게 되며 개발자의 실수로 인한 치명적인 피해를 줄일 수 있기 때문이다.
- 메모리는 무한한 자원이 아닌 유한한 자원이며 그 안에서 최대한 효율적으로 관리함에 있어 사용자들에게 서비스함에 있어 불편함이 없으며 따로 오토 스케일링이라는 작업에 대한 비용을 지불하지 않는 이점이 있기 때문이다.
Mark & Sweap

- Mark
- 사용되지 않는 객체 식별 작업
- Sweap
- 사용되지 않는 객체를 제거하는 작업
- Compaction
파편화
된 메모리 영역을 앞에서부터 채워 나가는 작업
GC의 메모리 수거 방법

- Object가 맨처음 생성되었을때, 그것은 Eden공간 내부에서 생성되고, 그 후 Minor GC후 살아남은 객체는 S1으로 이동, S1이 꽉 차면 Minor GC 이후 S2으로 이동된다. 이후 S2도 꽉 차게 되면 minor GC이후 다시 S1으로 이동한다. 그리고 이동하면서 살아남은 횟수를 기록하는 age bit가 +1씩 올라간다.
- 이런 식으로 S1, S2영역을 번갈아가며 살아남은 객체들은 Old 영역으로 옮겨진다.(이 과정을 Promotion이라고 한다.) 이 영역에서는 Major GC가 일어나며, 이 영역이 꽉 차면 Full GC가 일어난다.
- Young -> Old 영역으로 이동하는 기준은 Servivor영역이 꽉 차거나, Max age bit가 되었을 때 이다. (MaxTenuringThreshold 값이며, JVM 옵션 : -XX:MaxTenuringThreshold 로 설정할 수 있다.)
GC가 일어나는 경우 (상세하게)
GC는 Minor GC와 Major GC로 구분되는데요, 우선 Minor GC부터 알아보겠습니다.
새로운 객체가 Eden 영역에 할당이 됩니다. 그림과 같이 만약 더 이상 할당될 공간이 없다면 이때 Minor GC가 일어나게 되고 이때 Mark and Sweep이 발생하게 됩니다.
위 그림과 같이 Minor GC에서 살아남은 객체(파란색 박스)들은 Survivor 영역으로 이동합니다. 살아남지 못한 객체(하얀색 박스)들은 Sweep 과정을 통해 Eden 영역에서 삭제됩니다.
그 후 Survivor 영역에 있는 살아남은 객체들은 age 값이 1씩 증가합니다.
다시 Eden 영역이 꽉 차고 Minor GC가 발생하다 보면 Survivor 영역에 있는 객체의 age 값이 점점 증가하게 되겠죠?
이렇게 age 값이 증가하다가 객체의 age가 age threshold(임계점)에 도달하면 Old Generation으로 이동하게 됩니다.
이와 같은 과정을 반복하게 되어 Old generation이 꽉 차게 되면 Major GC가 발생하게 됩니다.
GC의 동작 순서
GC의 동작 순서는 다음과 같습니다.
- Stop the world
- Mark and Sweep, Compact
Stop the world
우선 Stop the world에 대해 알아보자면, GC를 실행하기 위해 JVM이 어플리케이션 실행을 멈추는 것입니다.
Stop the world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드들은 작업을 멈춥니다(stop).
그 후 GC 작업이 완료되면 중단했던 작업을 다시 시작합니다.
stop the world가 실행되면 mark and sweep이라는 알고리즘이 작동하게 됩니다.
Mark and Sweep, Compact

Mark는 GC 루트로부터 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾아서 마킹하는 과정입니다. 즉, 위에서 설명한 Reachable Objects와 Unreachable Objects를 식별하는 과정입니다.
Sweep은 Unreachable Objects를 heap에서 제거하는 과정입니다.
그리고 알고리즘에 따라서 Compact 과정이 추가되기도 합니다.
Compact는 Sweep 후 heap 영역에 듬성듬성 분산되어 남아 있는 객체들을 한 곳에 모아 메모리 단편화를 막아주는 작업입니다.
그래서 GC가 한번 수행될 때마다 Mark and Sweep, Compact 과정까지 한 사이클이 돌아가게 됩니다.
Major GC와 Minor GC로 나누어진 이유
이렇게 나누어진 이유는 GC가 두 가지 가설하에 만들어졌기 때문
- 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다. (금방 garbage가 된다)
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
그러므로 Minor GC는 매우 빈번하게 일어날 것이고, Major GC는 자주 일어나지도 않고, 일어나더라도 많은 객체가 정리되지 않기 때문에 분리했다고 볼 수 있습니다.
GC 종류 및 설명
GC 종류

- Serial GC
Mark-Sweep-Compaction 알고리즘을 사용하여, 순차적으로 동작한다. 쓰레드가 하나로 작용한다. CPU코어가 하나 일때 사용
- Mark-Sweep-Compaction Algorithm
이 알고리즘의 첫 단계는 Old 영역에 살아 있는 객체를 식별(Mark)하는 것이다. 그 다음에는 힙(heap)의 앞 부분부터 확인하여 살아 있는 것만 남긴다(Sweep). 마지막 단계에서는 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다(Compaction).
- Parallel GC
SerialGC 와 동작은 같지만, Minor GC만 멀티 스레드로 동작한다. (-XX:ParallelGCThreads=N 로 스레드 수를 조절)
- CMS GC
stw 시간을 최소화 하는데에 초점을 맞춘 GC. GC 대상을 최대한 자세히 파악한 후, 정리하는 시간(stw)을 짧게 가져가겠다는 컨셉이다.
다만 GC 대상을 파악하는 과정이 복잡한 여러단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높고, Compaction이 되지 않기 때문에 데이터 파편화로 인해 메모리부족 현상이 있을 수 있다. 따라서 fullGC시간이 굉장히 길다.

1) Initial Mark : Root Set에 의해 직접 참조되는 객체들을 선택 (Root Set: 현재 참조중인 객체를 판단하기 위한 메모리 주소 모음) - STW
2) Concurrent Mark : 애플리케이션 동작 중 살아있는 객체 식별
3) Remark: 2번에서 새로 추가로 참조가 끊긴 객체를 확인 - STW
4) Concurrent Sweep : 참조가 끊긴 모든 객체를 정리
- G1 GC (Garbage First) (java7~)
G1 GC는 앞서 살펴본 GC와는 다른 방식으로 힙 메모리를 관리한다. 앞서 살펴보았던 Eden, Survivor, Old 영역이 존재하지만 고정된 크기로 고정된 위치에 존재하는 것이아니며, 전체 힙 메모리 영역을 Region 이라는 특정한 크기(2048개의 regions)로 나눠서 각 Region의 상태에 따라 그 Region에 역할(Eden, Survivor, Old)이 동적으로 부여되는 상태이다.

Humongous : Region 크기의 50%를 초과하는 큰 객체를 저장하기 위한 공간이며, 이 Region 에서는 GC 동작이 최적으로 동작하지 않는다. (있으면 설계를 다시 살펴봐야한다 함..)

Initial Mark : Old Region 에 존재하는 객체들이 참조하는 Survivor Region 을 찾는다. - STW
Root Region Scan : Initial Mark 에서 찾은 Survivor Region에 대한 GC 대상 객체 스캔 작업을 진행한다.
Concurrent Mark : 전체 힙의 Region에 대해 스캔 작업을 진행하며, GC 대상 객체가 발견되지 않은 Region 은 이후 단계를 처리하는데 제외되도록 한다.
Remark : 최종적으로 GC 대상에서 제외될 객체(살아남을 객체)를 식별해낸다. - STW
Cleanup : 살아있는 객체가 가장 적은 Region 에 대한 미사용 객체 제거 수행한다. 이후 STW를 끝내고, 앞선 GC 과정에서 완전히 비워진 Region 을 Freelist에 추가하여 재사용될 수 있게 한다. - STW
Copy : GC 대상 Region이었지만 Cleanup 과정에서 완전히 비워지지 않은 Region의 살아남은 객체들을 새로운(Available/Unused) Region 에 복사하여 Compaction 작업을 수행한다.
java10부터는 Parallel Full GC for G1기능이 반영되어 Full GC를 병렬로 만들어, G1의 worst-case latency가 개선됨