[Java] JVM GC의 처리 과정

devdo·2022년 1월 16일
0

Java

목록 보기
3/59
post-thumbnail
post-custom-banner

GCGarbage Collection의 약자다.

한마디로 정리하면, 쓰레기 객체 정리자이다.

Java Runtime시 Heap 영역에 저장되는 객체들은 따로 정리하지 않으면 계속해서 메모리에 쌓이게되어 OutOfMemmory Exception이 발생하여 WAS 같은 경우 WAS가 다운될 수가 있다.

이를 방지하기 위하여 JVM에서는 주기적으로 사용하지 않는 객체를 수집하여 정리하는 게 GC의 역할이다.

개발자들은 이 GC가 어떤 주기로 돌아가는지는 모른다.


GC의 동작 알고리즘(Mark&Sweep)

GC의 수거 대상은 GC Roots로부터 참조를 탐색했을 때, Unreachable한 Object들이다.

GC Roots

  • stack 영역의 데이터들
  • method 영역의 static 데이터들
  • JNI에 의해 생성된 객체들

다음 순서로 GC가 동작이 된다.

1) Marking - GC Root로 부터 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.

2) Sweep - Unreachable한 객체들을 Heap에서 제거한다.

3) Compact (optional) - Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 나눈다.


GC가 처리하는 Heap 메모리 영역

Eden영역부터 Servior영역까지를 Young 영역이라고 부른다.

GC가 인식하는 힙영역을 크게 Young, Old, Perm 영역으로 나눌 수 있다.

이중 Perm(Permanent) 영역은 거의 사용하지 않는 영역으로서 클래스와 자바 언어 레벨에서 사용되지 않느다.


이미지 출처 : https://mirinae312.github.io/develop/2018/06/04/jvm_gc.html


그러니 우리가 고려해야 할 자바의 메모리 영역은 총 4개의 영역으로 나뉜다. ( Young (1. Eden, 2. Survivor0 1, 3. Survivor1 2), Old (4. 메모리) )


GC가 처리하는 과정을 간단하게 정리하면 이렇다.

1) 객체가 생성되어 Eden 영역에 올라간다.
2) Eden 영역이 꽉 차면 Survivor0 영역으로 넘어간다. 단 Survivor 영역들 중 하나는 반드시 비어있어야 한다.
3) 이 과정에서 오랫동안 살아남은 객체는 Old 영역으로 이동한다.

1) 객체가 생성되어 Eden 영역에 올라간다.

처음 생성된 객체는 Eden 영역에 할당된다. 이후 Eden영역이 꽉 찬다면 할당이 해제되지 않은 객체를 Servior 영역으로 이동시킨다.

2) Eden 영역이 꽉 차면 Survivor영역으로 넘어간다. 단 Survivor 영역중 하나는 반드시 비어있어야 한다.

Eden 영역이 꽉차면 Minor GC가 발생된다. Mark 과정에서 살아남은 객체들은 Survivor0 영역으로 복사하고, Eden 영역에 있는 데이터들을 삭제한다.

Survivor 영역에 있는 객체는 올라가있는 Survivor 영역이 꽉 찰 때 다시 GC 심사(Minor GC)를 받는다. 즉, Eden영역과 Survivor0 영역을 모두 mark하고 살아남은 객체들은 Survivor1 영역으로 복사하고, Eden 영역과, Survivor0 영역의 데이터를 삭제한다. 복사가 될 때는 해당 객체의 Age값이 증가된다.

단, 여기서 Survivor 영역을 거치지 않고 바로 Old 영역으로 이동하는 경우가 있다. 바로 객체의 크기가 Survivor 영역의 크기보다 큰 경우다.

3) 이 과정에서 오랫동안 살아남은 객체는 Old 영역으로 이동한다.

특정 age에 도달한 객체들은 Old generation 영역으로 옮겨진다. Young generation에서 Old generation으로 옮겨지는 현상을 promotion 이라한다.

Old 영역에도 데이터가 가득차면 Major GC가 발생한다.


GC의 종류

GC는 크게 Major GCMinor GC로 나눌 수 있다.
그 다음 Full GC로도 나눌 수 있다.

  • Major GC : Old, Perm 영역에서 발생하는 GC
  • Minor GC : Young(Eden, Survivor) 영역에서 발생하는 GC
  • Full GC : 메모리 전체를 대상으로 하는 GC

스레드 로컬 할당 버퍼

이 두가지 GC가 어떻게 상호 작용하느냐에 따라서 GC 방식에 차이가 나며, 성능에도 영향을 줄 수 있다. GC가 발생하거나 객체가 각 영역에서 다른 영역으로 이동할 때 애플리케이션 병목현상이 발생하면서 성능에 영향을 끼치게 된다.

  • stop-the-world : GC를 실행하는 쓰레드 외의 모든 쓰레드가 작업을 중단하는 것

그래서 핫 스팟(Hot Spot) JVM에서는 스레드 로컬 할당 버퍼(TLABs: Thread-Local Allocation Buffers)라는 것을 사용한다. 이를 통해 각 스레드별 메모리 버퍼를 사용하면 다른 스레드에 영향을 주지 않는 메모리 할당 작업이 가능해진다.


GC의 방식의 종류

1. Serial GC

  • GC를 처리하는 쓰레드가 1개(싱글 쓰레드)
  • 다른 GC에 비해 stop-the-world 시간이 길다
  • Mark-Compact 알고리즘 사용

2. Parallel GC

  • Java 8의 default GC
  • Young Generation의 GC를 멀티 쓰레드로 수행
  • Serial GC에 비해 stop-the-world 시간 감소

3. Parallel Old GC

  • Parallel GC를 개선
  • Old Generation에서도 GC를 멀티 쓰레드로 수행
  • Mark-Summary-Compact 알고리즘 사용

4. CMS (Concurrent Mark Sweep) GC

  • stop-the-world 시간을 줄이기 위해 고안됨
  • compact 과정이 없음
  • initial Mark - GC Root에서 참조하는 객체들만 식별
  • Concurrent Mark - 이전 단계에서 식별한 객체들이 참조하는 모든 객체 추적
  • Remark - 이전 단계에서 식별한 객체를 다시 추적, 추가되거나 참조가 끊긴 객체 확정
  • Concurrent Sweep - unreachable 객체들을 삭제

5. G1 (Garbage First) GC

  • CMS GC를 개선
  • Java 9+의 default GC
  • Heap을 일정한 크기의 Region으로 나눔
    => 바둑판의 각 영역에 객체를 할당하고 GC를 실행
  • 전체 Heap이 아닌 Region 단위로 탐색, 그래서 전체영역(Eden, Survival, Old Generation) 다 탐색하지 않아서 빨라!
  • compact 진행


출처

profile
배운 것을 기록합니다.
post-custom-banner

0개의 댓글