지난 글에서는 GC의 동작 방식에 대해서 알아보았다. 이번에는 [Mark and Sweep] 두번째 특징 중 "어플리케이션 실행과 GC의 실행이 병행된다." 는 것이 있었다.
Major GC같은 경우에는 속도가 느려 Stop-The-World 문제로 인해 어플리케이션 실행이 지연되는 문제점이 발생하기 된다.
이 문제를 해결하기 위해 많은 알고리즘이 고안되었다고 한다.
이번 글에서는 GC알고리즘에 대해서 알아본다.
목표 : Stop The World 시간을 최소화하자!!
여러 개의 쓰레드로 GC를 실행
멀티코어 환경에서 사용
Java8의 default GC방식
Serial GC와 기본적인 알고리즘은 같지만, Young 영역의 Minor GC를 멀티 쓰레드로 수행 (Old 영역은 여전히 싱글 쓰레드)
- Old 영역에서도 멀티 쓰레드로 GC 수행
Serial GC에 비해 stop-the-world 시간 감소
Initial Mark -> Concurrent Mark -> Remark -> Concurrent Sweep
GC 과정에서 살아남은 객체를 탐색하는 시작 객체(GC Root)에서 참조 Tree상 가까운 객체만 1차적으로 찾아가며 객체가 GC 대상(참조가 끊긴 객체)인지를 판단한다. 이 때는 STW 현상이 발생하게되지만, 탐색 깊이가 얕기 때문에 STW 발생 기간이 매우 짧다.
STW 현상없이 진행되며, Initial Mark 단계에서 GC 대상으로 판별된 객체들이 참조하는 다른 객체들을 따라가며 GC 대상인지를 추가적으로 확인한다.
Concurrent Mark 단계의 결과를 검증한다.
Concurrent Mark 단계에서 GC 대상으로 추가 확인되거나 참조가 제거되었는지 등등의 확인을 한다.
이 검증과정은 STW 를 유발하기 때문에 STW 지속시간을 최대한 줄이기 위해 멀티스레드로 검증 작업을 수행한다.
STW 없이 Remark 단계에서 검증 완료된 GC 객체들을 메모리에서 제거한다.
G1 GC에서는 그동안 봐왔던 Heap 영역에서 보지 못한 Humongous, Available/Unused 이 존재하며 두 Region에 대한 역할은 아래와 같다.
Humongous : Region 크기의 50%를 초과하는 큰 객체를 저장하기 위한 공간이며, 이 Region 에서는 GC 동작이 최적으로 동작하지 않는다.
Available/Unused : 아직 사용되지 않은 Region을 의미한다.
G1 GC에서 Young GC 를 수행할 때는 STW(Stop-The-World) 현상이 발생하며, STW 시간을 최대한 줄이기 위해 멀티스레드로 GC를 수행한다. Young GC는 각 Region 중 GC대상 객체가 가장 많은 Region(Eden 또는 Survivor 역할) 에서 수행 되며, 이 Region 에서 살아남은 객체를 다른 Region(Survivor 역할) 으로 옮긴 후, 비워진 Region을 사용가능한 Region으로 돌리는 형태 로 동작한다.
G1 GC에서 Full GC 가 수행될 때는 Initial Mark -> Root Region Scan -> Concurrent Mark -> Remark -> Cleanup -> Copy 단계를 거치게된다.
Old Region 에 존재하는 객체들이 참조하는 Survivor Region 을 찾는다. 이 과정에서는 STW 현상이 발생하게 된다.
Initial Mark 에서 찾은 Survivor Region에 대한 GC 대상 객체 스캔 작업을 진행한다.
전체 힙의 Region에 대해 스캔 작업을 진행하며, GC 대상 객체가 발견되지 않은 Region 은 이후 단계를 처리하는데 제외되도록 한다.
애플리케이션을 멈추고(STW) 최종적으로 GC 대상에서 제외될 객체(살아남을 객체)를 식별해낸다.
애플리케이션을 멈추고(STW) 살아있는 객체가 가장 적은 Region 에 대한 미사용 객체 제거 수행한다. 이후 STW를 끝내고, 앞선 GC 과정에서 완전히 비워진 Region 을 Freelist에 추가하여 재사용될 수 있게 한다.
GC 대상 Region이었지만 Cleanup 과정에서 완전히 비워지지 않은 Region의 살아남은 객체들을 새로운(Available/Unused) Region 에 복사하여 Compaction 작업을 수행한다.
각 GC마다 실행 명령어를 참고하자.
그래서 어떤 알고리즘을 사용해야 할까?
나의 결론은 이러한 GC의 종류가 있음을 알았다.
그러면 나는 내가 직접 java 에서 GC를 설정하고, 각 GC마다 성능 Test를 통해 가장 좋은 성능이 나오는 것을 취하려고 해봐야 할 것 같다.
출처