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 과정까지 한 사이클이 돌아가게 됩니다.
- minor GC : Young 영역에서 발생하는 GC - major GC : Old 영역이나 Perm 영역에서 발생하는 GC / (FULL GC)
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가 개선됨