JVM의 메모리구조를 파악하는거를 넘어서, 거기서 더 더 깊게 GC에 대해 알아보도록 한다.

우선 일반적인 GC의 과정에 대해 설명한다.

Yong 영역의 GC

  1. 객체 생성시 Eden(New) 영역에 저장이 된다.
  2. Eden영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동한다.
  3. Eden영역에서 GC가 발생하면, 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.
  4. 하나의 Survivor 영역이 가득 차게 되면, 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득 찬 Survivor영역은 빈 상태로 돌아간다.
  5. Survivor에 살아남은 데이터가 어느정도 age가 차게 되면 Old영역으로 넘어간다. 이 경우를 Major GC라고 한다.
    => 각 객체는 Minor GC에서 살아남은 횟수를 기록하는 age bit를 가지고 있으며, Minor GC가 발생할 때마다 age bit값은 1씩 증가된다. 여기서 age bit 값이 MaxTenuringThreshold라는 설정 값을 초과할 경우 Old Generation영역으로 객체가 이동되는 것이다.(물론 age bit가 초과하기 전이더라도, Survivor 영역의 메모리가 부족할 경우 미리 Ole영역으로 이동한다)

Old영역의 GC

GC는 크게

  1. Stack이 사용하는, 참조하는 변수를 찾아 현재 사용중인 객체를 탐색하는 mark작업을 수행한다.
  2. marking이 되지 않은 객체는 소멸하는 sweep 작업을 진행한다.
  3. 메모리의 파편화 된 부분을 채워나가는 compaction작업을 수행한다.

다양한 GC

Serial GC

이름그대로 순차적인 GC방식이다. Serial GC는 데스크톱의 CPU코어가 하나만 있을 때 사용하기 위해 만든 방식이다.(싱글스레드)
Serial GC는 Mark-Sweep-Compaction 알고리즘을 통해 작동된다.

Mark-Sweep-Compatction GC가 사용되지 않는 객체를 메모리에서 제거하는 과정인만큼, GC 대상객체를 식별하고 제거하며, 객체가 제거되 파편화된 메모리 영역을 앞에서부터 채워나가는 작업을 수행하게 된다.

  • 사용되지 않는 객체를 식별하는 작업(Mark)
  • 사용되지 않는 객체를 제거하는 작업(Sweep)
  • 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)

Parallel GC

Parallel GC는 Serial GC와 기본적인 알고리즘은 같다. 그러나 Serial GC는 GC를 처리하는 스레드가 하나인 것에 비해, Parallel GC는 GC를 처리해주는 쓰레드가 여러개이다. 그렇기 때문에 Serial GC보다 빠르게 객체를 처리할 수 있다.
Parallel GC는 메모리가 충분하고, 코어의 개수가 많을때 유리하다.


Parallel Old GC

Parallel Old GC는 JDK 5 update 6부터 제공하는 GC방식이다. 앞서 말한 Parallel GC와 거의 비슷하지만, Old 영역의 GC 알고리즘만 다르다. Parallel Old GC는 Mark-Summary-Compaction단계를 거친다. Summary 단계는 앞서 GC를 수행한 영역에 대해 별도로 살아있는 객체를 식별한다는 점에서 Mark-Sweep-Compaction 알고리즘의 Sweep단게와는 다르며, 더 복잡한 절차를 거친다.

CMS GC

CMS는 GC과정에서 발생하는 STW시간을 최소화 하는데 초점을 맞춘 GC 방식이다.
더 정확히는, GC 대상을 최대한 자세히 파악한 후, 정리하는 시간(STW)시간을 최소화 하는 컨셉이다. 하지만 GC대상을 파악하는 과정이 복잡한 여러 단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높다.

위 그림에서 볼 수 있듯이, CMS GC는 지금까지 나온 GC보다 더 복잡하다.
CMS GC는 크게 4가지 단계를 거친다.

  • Initial Mark : 클래스 로더에 가장 가까운 객체 중, 살아있는 객체만 찾는 것으로 끝난다. 그렇기 때문에 멈추는 시간은 매우 짧다. (STW)
  • Concurrent Mark : 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가며 확인한다. 이 단계에서의 특징은 다른 스레드가 실행중인 상태에서 동시에 진행된다는 것이다.
  • Remark: Concurrent Mark 단계에서 새로 참조되거나 참조가 끊긴 객체를 확인한다. (STW)
  • Concurrent Sweep: 최종적으로 사용하지 않는 객체를 정리한다. 이 작업또 다른 스레드가 실행되고 있는 상황에서 진행한다.

이렇게 여러 단계에서 짧게, 그리고 동시에 진행되기에 stop-the-world 시간이 매우 짧다. 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 불린다. 하지만, CMS GC는 아래와 같은 단점이 존재한다.

  • GC의 풀사이클 기준으로는 Parallel GC보다 길다.
  • Mark and sweep 방식보다 복잡하다 보니 다른 GC방식보다 CPU와 메모리를 더 많이 사용한다.
  • Old 영역에서 Compaction 단계가 기본적으로 제공되지 않아 메모리 단편화가 발생할 수 있다.

따라서, CMS GC를 사용할 때에는 신중히 검토한 후 사용해야 한다. 그리고 조각난 메모리가 많아 Compaction 작업을 실행하면, 다른 GC 방식보다 stop-the-world 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 한다.

G1 GC

Java 9에서 Default로 사용되고 있는 GC 방식이다. G1 GC는 지금까지 생각했던 Young 영역 / Old 영역과는 다르다.

  • Minor GC : STW가 발생하며, 속도를 줄이기 위해 멀티스레드로 수행한다. Minor GC는 각 Region중 GC할 객체가 가장 많은 Region에서 수행되며, 이 Region에서 살아남은 객체를 다른 Region으로 옮긴 후, 비워진 Region을 사용가능한 상태로 돌리는 형태를 띈다.
  • Major GC :
    • Initial Mark : Old Region에 존재하는 객체들이 참조하는 Survivor Region을 찾는다 (STW)
    • Root Region Scan : Initial Mark에서 찾은 Survivor Region에 대한 GC 대상 객체 스캔 작업을 수행한다.
    • Concurrent Mark : 전체 Heap의 Region에 대해 스캔 작업을 진행하며, GC대상 객체가 발생하지 않은 Region은 이후 단계를 진행하지 않도록 제외시킨다.
    • Remark : 최종적으로 GC대상에서 제외할 객체를 식별한다.(STW).
    • Cleanup : 살아있는 객체가 가장 적은 Region에서 미사용 객체를 제거한다. (STW) 그 후 , 완전히 비워진 Region을 사용가능한 상태로 되돌린다.
    • Copy : GC 대상 Region이었지만, 완전히 비워지지 않은 Region 내 살아남은 객체들을 새로운 Region(Available, Unused)에 넣어 Compaction 작업을 수행한다.

G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다. 그러다가, 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다. 즉, 지금까지 Young의 세가지 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC방식이다.

G1 GC의 가장 큰 장점은 성능이다. 지금까지 설명한 GC중 그 어떤것 보다 제일 빠르다. 큰 힙 메모리에서 짧은 GC를 보장하는데 그 목적을 둔다.


참고 자료 :

  1. Java Garbage Collection
  2. JVM 구조와 자바 런타임 메모리 구조

oksusutea's blog

꾸준히 기록하려고 만든 블로그