본문 바로가기

Java/Java

(JAVA) Garbage Collector

GC의 원리

  • 메모리 할당
  • 사용 중인 메모리 인식
  • 사용하지 않는 메모리 인식

만약 GC가 사용하지 않는 메모리를 인식하지 않는다면 할당된 메모리 영역이 꽉 차 버려 JVM에 Hang이 걸리거나 더 많은 메모리를 할당하려고 할 것이다.

또한 더 이상 사용 가능한 메모리 공간이 없음에도 불구하고 할당하려고 한다면 OOM이 발생하게 된다.


크게 Young, Old, Perm 세 영역으로 나뉘고 Perm 영역은 JDK8 버전 이후로는 사라지게 된다.

따라서 우리가 고려해할 메모리 영역은 아래와 같이 총 네 개 영역이다.

 

-----------------Young 영역----------------- -----------old 영역-----------
​ Eden | Survivor 1 | Survivor 2 | 메모리 영역
-----------------Young 영역----------------- -----------old 영역-----------

 

Eden 영역에 데이터가 꽉 차게 되면 Survivor 영역으로 해당 객체로 옮겨지게 된다. (이때 두 Survivor 영역 중 하나는 반드시 비어있어야 한다.)

비어있던 영역의 Survivor는 Eden영역에서 GC 이후에 살아남은 객체들이 이동하게 된다.

근데 이때 Survivor 영역을 거치지 않고 바로 Old 영역으로 이동하는 경우가 있다. 예를 들어서 Survivor 영역의 크기가 16MB인데 20MB의 크기인 객체를 Eden 영역에서 옮겨갈 때이다. 이때는 바로 Old 영역으로 이동하게 된다.


GC의 종류

  • 마이너 GC : Young 영역에서 발생하는 GC
  • 메이저 GC : Old 영역 혹은 Perm에서 발생하는 GC

메이저 GC와 마이너 GC의 상호작용이 성능에 지대한 영향을 끼친다.

각 영역으로 이동할 시 만약 병목현상이 발생한다면 성능에 악영향을 끼칠 수 있기 때문에 핫스폿 JVM에서는 스레드 로컬 할당(TLABs)이라는 것을 사용하여 각 스레드 별 메모리 버퍼를 사용하면 다른 스레드에 영향을 주지 않는 메모리 할당 작업이 가능하게 된다.


5가지 GC의 방식

  • Serial Collector
  • Parallel Collector
  • Parallel Compacting Collector
  • Concurrent Mark Sweep Collector
  • Carbage First Collector

Serial Collector

: Young 영역과 Old 영역이 서로 연속적으로 처리되며 하나의 CPU를 사용하는 방식이다.

  1. Eden 영역이 꽉 차게 되면 To Survivor로 살아 있는 객체가 이동한다.

    • 이때 너무 큰 객체는 바로 Old 영역으로 이동
    • From Survivor 영역의 살아있는 객체 또한 To Survivor 영역으로 이동한다.
  2. To Survivor 영역이 꽉 찼을 경우 Eden 영역 혹은 From Survivor 영역에 남아있는 객체들을 Old 영역으로 내보낸다.

    • Old 영역 혹은 Perm 영역의 객체들은 Mark-Sweep-Compact 컬렉션 알고리즘을 따른다.

      • 간단하게 말하자면 쓰이지 않는 객체를 표시하여 삭제하고 한 곳으로 모으는 알고리즘을 뜻한다.
      1. Old 영역으로 이동된 객체들 중 살아있는 객체를 식별
      2. Old 영역의 객체들을 훑는 작업을 하여 쓰레기 객체를 식별
      3. 필요 없는 객체들은 지우고 살아있는 객체를 한 곳으로 모은다.
  3. 명령어 : -XX:+UseSerialGC


병렬 콜렉터

해당 방식의 목표는 CPU가 대기상태로 남아있는 것을 최소화하는 것이다.

시리얼 컬렉터는 Young 영역에서 컬렉션을 순차적으로 처리하지만 해당 방식은 병렬로 처리한다.

  • 이로 인해 GC의 부하를 줄이고 애플리케이션 처리량을 증가시킬 수 있다.
  1. Mark-Sweep-Compact 컬렉션 알고리즘을 사용

  2. 명령어 : XX:+UseParallelGC


병렬 콤팩팅 콜렉터

병렬 콜렉터와 거의 동일하지만 다른 점은 Old 영역에서 다른 알고리즘을 사용한다는 것이다.

 

  1. 표시 단계 : 살아있는 객체를 식별하여 표시
  2. 종합 단계 : 이전에 GC를 수행하여 컴 펙션 된 영역에 살아있는 객체의 위치를 조사
  3. 컴 펙션 단계 : 컴 펙션을 수행
    • 이후 컴 펙션 된 영역과 비어있는 영역으로 나누어진다.


CMS 콜렉터

힙 메모리 영역의 크기가 클 때 적합하다.

Young 영역에 대한 GC는 병렬 콜렉터와 동일

 

CMS 컬렉터는 Serial/Throughput 컬렉터가 Full GC 때 Long Pause 되는 걸 막기 위해 디자인되었다. Full GC 동안 애플리케이션 스레드를 멈추는 대신 하나 이상의 백그라운드 스레드가 주기적으로 Old 영역을 스캔하고 사용되지 않는 객체를 제거한다. 그리고 동일하게 Minor GC 때 애플리케이션 스레드 전체를 멈춘다.

 

그렇지만 CPU 소모량이 많고, 백그라운드 스레드는 정렬(compaction)을 하지 못하기 때문에 메모리 단편화가 생긴다. 그리고 CMS 백그라운드 스레드가 작업을 완료할 만큼 충분한 CPU 얻지 못하거나 힙에 메모리 단편화가 많으면 CMS는 Serial Collector로 동작한다. 즉 싱글 스레드를 사용해 전체 애플리케이션 스레드를 멈추고 Old Generation 영역을 정리한다. 그런 후 작업이 끝나면 다시 CMS 컬렉터로 동작한다.

 

Old 영역의 단계를 살펴보면 총 네 가지 단계를 가진다.

  1. 초기 표시 단계 : 매우 짧은 대기 시간으로 살아 있는 객체를 찾는 단계
    • 클래스 로더에 가장 가까운 객체 중 살아있는 객체만을 찾는다. (Stop-the-world가 굉장히 짧다.)
  2. 컨커런트 표시 단계 : 서버 수행과 동시에 살아있는 객체에 표시를 하는 단계
    • 초기 표시 단계에서 살아있다고 판단한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다.
      • 이 단계에서의 특징은 다른 스레드가 실행 중인 상태에서 동시에 진행된다.
  3. Remark(재표시) 단계 : 컨커런트 표시 단계에서 표시하는 동안 변경된 객체에 대해서 다시 표시하는 단계
    • 컨커런트 표시 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인
  4. 컨커런트 스윕 단계 : 표시되어 있는 garbage를 정리하는 단계

CMS는 컴 펙션 단계를 거치지 않기 때문에 왼쪽으로 메모리를 몰아 놓는 작업을 수행하지 않는다.

또한 다른 GC 방식보다 메모리와 CPU를 많이 잡아먹는다.

 

CMS 콜렉터 방식은 2개 이상의 프로세서를 사용하는 서버에 적당하다. (웹 서버)

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


G1 GC

CMS GC의 파편화 현상을 보완하기 위해 등장

이전의 다른 GC들과 달리 G1 GC는 Young 영역과 Old 영역이 물리적으로 나뉘어 있지 않으며 각각의 영역은 바둑판 모양으로 되어있는데 각 바둑판의 사각형 영역을 region이라고 일컫는다. 이때 각각의 region의 크기는 동일하며 기본 크기 1M에서 최대 32MB까지 지정이 가능하다.

이러한 바둑판 모양의 구역이 각각 Eden, Survivor, Old 영역의 역할을 번갈아가면서 하고 Humongous라는 영역도 포함되게 된다.

G1에서 Young GC

  1. 몇 개의 region을 선정하여 Young 영역으로 지정한다.
  2. 이러한 선형적이지 않은 구역에 객체가 생성되면서 데이터가 쌓인다.
  3. Young 영역으로 할당된 region에 데이터가 꽉 차면 GC를 수행한다.
  4. GC를 수행하면서 살아있는 객체들만 Survivor 영역으로 이동시킨다.

이러한 과정이 반복되며 몇 번의 Young GC에도 객체가 살아있으면 Old 영역으로 이동된다.

G1에서 Old GC

  • 초기 표시 단 게 : Old 영역에 있는 객체에서 Survivor 영역의 객체를 참조하고 있는 객체들을 표시
  • 기본 구역 스캔 단계 : Old 영역 참조를 위해 Survivor 영역을 훑는다. (해당 작업은 Young GC 이전에 수행)
  • 컨커런트 표시 단계 : 전체 힙 영역에 살아있는 객체를 찾는다. 만약 이때 Young GC가 발생하면 STOP 한다.
  • 재 표시 단계 : 힙에 살아있는 객체들을 표시하는 작업
    • Snapshot - at - the - beginning 알고리즘 사용 -> CMS 보다 빠르다.
  • 청소 단계 : 살아있는 객체와 비어있는 구역을 식별하여 필요 없는 객체들을 지운다.
  • 복사 단 게 : 살아있는 객체들을 비어있는 구역으로 이동시킨다.

G1 GC의 가장 큰 장점은 성능이다. 지금까지 설명한 어떤 GC 방식보다도 빠르다. 하지만, JDK 6에서는 G1 GC를 early access라고 부르며 그냥 시험 삼아 사용할 수만 있도록 한다. 그리고 JDK 7에서 정식으로 G1 GC를 포함하여 제공한다.

힙 영역이 매우 큰 머신에서 돌리기에 적합하다.

중간에 쓸모없는 객체를 쏙쏙 제거하는 CMS와 달리 한 Region을 통째로 정리해 버린다. 참조가 ㅇ벗는 객체들은 지우고 사용 중이 객체는 다른 Region으로 고스란히 복사하는 방식

다른 Region으로 사용 중인 객체만 옮김으로 CMS와 달리 메모리 파편화 현상이 일어나지 않는다는 장점이 있다.


References

https://d2.naver.com/helloworld/1329

자바 성능 튜닝 이야기
국내도서
저자 : 이상민
출판 : 인사이트 2013.10.26
상세보기