JDK 5.0이상에서 지원하는 GC 방식에는 네가지가 있다. WAS나 자바 애플리케이션 수행시 옵션을 지정하여 선택할 수 있다.

4가지 GC(가비지 콜렉터) 방식

  1. Serial GC (시리얼 콜렉터)
  2. Parallel GC (병렬 콜렉터)
  3. Parallel Compacting GC (Parallel Old GC, 병렬 컴팩팅 콜렉터)
  4. Concurrent Mark-Sweep GC (CMS 콜렉터)
  5. Garbage First GC (G1 GC)

JVM GC 동작 순서

요약하면 GC 동작은 아래 3 STEP으로 나눠진다.

  • Heap 영역에 존재하는 객체들에 대해 접근 가능한지 확인한다.
  • GC Root에서 부터 시작하여 참조값을 따라가며 접근 가능한 객체들에 Mark하는 과정을 진행한다.
  • Mark 되지 않은 객체 즉, 접근할 수 없는 객체는 제거(Sweep) 대상이 된고, 해당 객체들을 제거한다.

접근 가능한 객체 판단 과정

GC Root에서 시작해서 참조하는 객체를 찾고, 또 그 객체가 참조하는 객체를 찾아가며 Mark 한다.

Mark 되지 않은 객체는 접근할 수 없는 객체 (Unreachable Object)로 판단하고 메모리를 돌며 제거(Sweep)한다.

GC Root가 될 수 있는 대상

  • JVM 메모리의 Stack 영역에 존재하는 참조 변수
  • Method Area의 static 데이터
  • JNI에 의해 생성된 객체들

1. Serial GC

Young 영역과 Old 영역이 시리얼하게(연속적으로) 처리되며 하나의 CPU를 사용한다.

이 처리를 수행할 때를 Stop-the-world라고 표현한다. 다시 말하면, 콜렉션이 수행될 때 애플리케이션 수행이 정지된다.

  • 가장 단순한 방식의 GC로 싱글 스레드(스레드 1개)로 동작한다.
  • 싱글 스레드로 동작하여 느리고, 그만큼 Stop The World 시간이 다른 GC에 비해 길다.
  • Mark & Sweep & Compact 알고리즘을 사용
  • 보통 실무에서 사용하는 경우는 없음 (디바이스 성능이 안좋아서 CPU 코어가 1개인 경우에만 사용)

  1. 일단 살아있는 객체들은 Eden 영역에 있다.
  2. Eden 영역이 꽉차게 되면 To Survivor 영역(비어있는 영역)으로 살아 있는 객체가 이동한다. 이때 Survivor 영역에 들어가기에 너무 큰 객체는 바로 Old 영역으로 이동한다. 그리고 From Survivor 영역에 있는 살아 있는 객체는 To Survivor 영역으로 이동한다.
  3. To Survivor 영역이 꽉 찼을 경우, Eden 영역이나 From Survivor 영역에 남아 있는 객체들은 Old 영역으로 이동합니다.

이 이후에 Old 영역이나 Perm 영역에 있는 객체들은 Mark-sweep-compact 콜렉션 알고리즘을 따른다. 이 알고리즘에 대해서 간단하게 말하면, 안 쓰는 거 표시해서 삭제하고 한 곳으로 모으는 알고리즘이다.

Mark-sweep-compact 알고리즘

1) Old 영역으로 이동된 객체들 중 **살아 있는 개체를 식별**한다. (Mark)
2) Old 영역의 객체들을 훑는 작업을 수행하여 **쓰레기 객체를 식별**한다. (Sweep)
3) 필요 없는 객체들을 **지우고** 살아 있는 **객체들을 한 곳으로 모은다**. (Compact)

이렇게 작동하는 시리얼 콜렉터는 일반적으로 클라이언트 종류의 장비에서 많이 사용된다. 다시 말하면, 대기 시간이 많아도 크게 문제되지 않는 시스템에서 사용된다.

명시적 지정 방법

UseSerialGC

2. Parallel GC

이 방식은 throughput collector로도 알려진 방식이다. 이 방식의 목표는 다른 CPU가 대기 상태로 남아 있는 것을 최소화하는 것이다.

시리얼 콜렉터와 달리 Young 영역에서의 콜렉션을 병렬(Parallel)로 처리한다. 많은 CPU 를 사용하기 때문에 GC의 부하를 줄이고 애플리케이션의 처리량을 증가시킬 수 있다.

  • Java 8의 default GC
  • Young 영역의 GC를 멀티 스레드 방식을 사용하기 때문에, Serial GC에 비해 상대적으로 Stop The World 가 짧다
    • Old 영역은 아님
    • Old 영역의 GC는 시리얼 콜렉터와 마찬가지로 Mark-Sweep-Compact 콜렉션 알고리즘을 사용한다.

명시적 지정 방법

UseParallelGC

3. Parallel Compacting GC

병렬 콜렉터와 다른 점은 Old 영역 GC에서 새로운 알고리즘을 사용한다.

  • Parallel GC는 Young 영역에 대해서만 멀티 스레드 방식을 사용했다면, Parallel Old GC는 Old영역까지 멀티스레드 방식을 사용

그러므로 Young 영역에 대한 GC는 병렬 콜렉터와 동일하지만, Old 영역의 GC는 다음의 3단계를 거치게 된다.

  1. Mark 단계 : 살아 있는 객체를 식별하여 표시해 놓는 단계
  2. Sweep 단계 : 이전에 GC를 수행하여 컴팩션된 영역에 살아 있는 객체의 위치를 조사하는 단계
  3. Compact 단계 : 컴팩션을 수행하는 단계. 수행 이후에는 컴팩션된 영역과 비어 있는 영역으로 나뉩니다.

병렬 콜렉터와 동일하게 이 방식도 여러 CPU를 사용하는 서버에 적합하다. GC를 사용하는 스레드 개수는 -XX:+ParallelGCThreads=n 옵션으로 지정할 수 있다.

명시적 지정방법

UseParallelOldGC

ParallelGCThreads=n

4. Concurrent Mark-Sweep GC

Stop The World로 Java Application이 멈추는 현상을 줄이고자 만든 GCReacable 한 객체를 한번에 찾지 않고 나눠서 찾는 방식을 사용 (4 STEP)

이 방식은 low-latency collector로도 알려져 있으며, 힙 메모리 영역의 크기가 클 때 적합하다. Young 영역에 대한 GC는 병렬 콜렉터와 동일합니다. Old 영역의 GC는 다음 4단계를 거친다.

  1. Initial Mark : GC Root가 참조하는 객체만 마킹 (stop-the-world 발생)
    • 살아 있는 객체를 찾는 단계
  2. Concurrent Mark : 참조하는 객체를 따라가며, 지속적으로 마킹. (stop-the-world 없이 이루어짐)
    • 서버 수행과 동시에 살아 있는 객체에 표시를 해놓는 단계
  3. Remark : concurrent mark 과정에서 이 없는지 다시 한번 마킹하며 확정하는 과정. (stop-the-world 발생)
    변경된 사항
  4. Concurrent Sweep : 접근할 수 없는 객체를 제거하는 과정 (stop-the-world 없이 이루어짐)

위와 같이 stop-the-world가 최대한 덜 발생하도록 하여, Java Application이 멈추는 현상을 줄인다.

CMS는 컴팩션 단계를 거치지 않기 때문에 왼쪽으로 메모리를 몰아 놓는 작업을 수행하지 않는다. 그래서 GC 이후에 그림과 같이 빈 공간이 발생하므로, -XX:+CMSInitiatingOccupancyFraction=n 옵션을 사용하여 Old 영역의 %를 n 값에 지정합니다. (기본값 : 68)

CMS 콜렉터는 추가적인 옵션으로 점진적 방식을 지원한다. 이 방식은 Young 영역의 GC를 더 잘게 쪼개어 서버의 대기 시간을 줄일 수 있다. CPU가 많지 않고 시스템의 대기 시간이 짧아야 할 때 사용하면 좋다.점진적은 GC를 수행하려면 -XX:+CMSIncrementalMode 옵션을 지정하면 된다. JVM에 따라서는 -Xingc라는 옵션을 지정해도 같은 의미가 된다. 하지만 이 옵션을 지정할 경우 예기치 못한 성능 저하가 발생할 수 있으므로, 충분한 테스트를 한 후에 운영 서버에 적용해야 한다.

CMS GC는 2개 이상의 프로세서를 사용하는 서버에 적당하다. 가장 적당한 대상으로는 웹 서버가 있다.

명시적 지정방법

UseConcMarkSweepGC

CMSInitiatingOccupancyFraction=n

CMSIncrementalMode

CMS GC는 Java9 버젼부터 deprecated 되었고 결국 Java14에서는 사용이 중지되었다.

5. G1 GC

  • Java 9 부터 default GC
  • 현재 GC 중 stop-the-world의 시간이 제일 짧음
  • 어떠한 GC 방식보다 처리 속도가 빠르며 큰 메모리 공간에서 멀티 프로레스 기반으로 운영되는 애플리케이션을 위해 고안되었다. 또한 G1 GC는 다른 GC 방식의 처리속도를 능가한다.
  • CMS GC 를 개선하여 만든 GC로 위에서 살펴본 GC와는 다른 구조를 가진다.

  • Heap을 Region이라는 일정한 부분으로 나눠서 메모리를 관리한다.
    • 기존 GC처럼 물리적인 영역으로 나누지 않고, Region(지역)이라는 개념을 새로 도입하여 Heap을 균등하게 여러 개의 지역으로 나누고, 각 지역을 역할과 함께 논리적으로 구분하여(Eden 지역인지, Survivor 지역인지, Old 지역인지) 객체를 할당한다.
  • G1 GC에서는 Eden, Survivor, Old 역할에 더해 Humongous와 Availalbe/Unused라는 2가지 역할을 추가하였다. Humongous는 Region 크기의 50%를 초과하는 객체를 저장하는 Region을 의미하며, Availabe/Unused는 사용되지 않은 Region을 의미한다.
  • G1 GC의 핵심은 전체 Heap에 대해서 탐색하지 않고 부분적으로 Region 단위로 탐색하여, 가비지가 많은 Region에만 우선적으로 GC를 수행하는 것이다.
  • G1 GC도 다른 가비지 컬렉션과 마찬가지로 2가지 GC(Minor GC, Major GC)로 나누어 수행된다.

1. Minor GC

한 지역에 객체를 할당하다가 해당 지역이 꽉 차면 다른 지역에 객체를 할당하고, Minor GC가 실행된다. G1 GC는 각 지역을 추적하고 있기 때문에, 가비지가 가장 많은(Garbage First) 지역을 찾아서 Mark and Sweep를 수행한다.

Eden 지역에서 GC가 수행되면 살아남은 객체를 식별(Mark)하고, 메모리를 회수(Sweep)한다. 그리고 살아남은 객체를 다른 지역으로 이동시키게 된다. 복제되는 지역이 Available/Unused 지역이면 해당 지역은 이제 Survivor 영역이 되고, Eden 영역은 Available/Unused 지역이 된다.

2. Major GC(Full GC)

시스템이 계속 운영되다가 객체가 너무 많아 빠르게 메모리를 회수 할 수 없을 때 Major GC(Full GC)가 실행된다. 그리고 여기서 G1 GC와 다른 GC의 차이점이 두각을 보인다.

기존의 다른 GC 알고리즘은 모든 Heap의 영역에서 GC가 수행되었으며, 그에 따라 처리 시간이 상당히 오래 걸렸다. 하지만 G1 GC는 어느 영역에 가비지가 많은지를 알고 있기 때문에 GC를 수행할 지역을 조합하여 해당 지역에 대해서만 GC를 수행한다. 그리고 이러한 작업은 Concurrent하게 수행되기 때문에 애플리케이션의 지연도 최소화할 수 있는 것이다.

물론 G1 GC는 다른 GC 방식에 비해 잦게 호출될 것이다. 하지만 작은 규모의 메모리 정리 작업이고 Concurrent하게 수행되기 때문이 지연이 크지 않으며, 가비지가 많은 지역에 대해 정리를 하므로 훨씬 효율적이다.

명시적 지정방법

UseG1GC

GC 방식 비교

네 종류 GC 방식에 대한 성능 및 기능 비교 표

위의 내용을 종합하면, GC 수행 시점은 다음과 같다.

  1. 각 영역의 할당된 크기의 메모리가 허용치를 넘을 때
  2. 개발자가 컨트롤할 영역은 아님

개발자라면, 자바의 GC 방식을 외우면서 개발하거나 서버를 세팅할 필요는 없다. 이해만 하고 있으면 된다. 단, 필요할때(시스템 오픈 전 성능 테스트 / 서버 세팅 시), 알맞은 GC 방식을 개발한 시스템에 적용하면 된다고 한다.

참고

https://dar0m.tistory.com/261

'링크모음' 카테고리의 다른 글

CI/CD  (0) 2022.08.16
JVM  (0) 2022.08.16
스트림 API  (0) 2022.08.16
모듈시스템  (0) 2022.08.16
자바  (0) 2022.08.16

+ Recent posts