Skip to main content

7. 가비지 수집 고급

핫스팟 수집기에 대해 소개합니다.


트레이드오프와 탈착형 수집기#

개발자는 가비지 수집기 선정 시 다음 항목을 고려해야합니다.

  • 중단 시간(중단 길이 또는 기간이라고도 합니다.)
  • 처리율(애플리케이션 런타임 대비 GC 시간)
  • 중단 빈도(수집기 때문에 애플리케이션이 얼마나 자주 멈추는지)
  • 회수 효율(GC 사이클 당 얼마나 많은 가비지가 수집되는지)
  • 중단 일관성(중단 시간이 고른편입니다.)

성능 엔지니어는 수집기 선정 시 다양한 트레이드 오프와 관심사를 면밀히 검토해야합니다.


동시 GC 이론#

그래픽/애니메이션 디스플레이 시스템와 같은 특화된 시스템은 프레임률이 거의 고정되어 있어서 GC를 규칙적으로 수행할 수 있습니다.

적절한 계산이 지연되는 것은 사실 사소한 단점에 불과합니다. 정작 큰 문제는 가비지 수집이 언제 발생할지 예상할 수 없다는 점입니다.

최신 GC이론은 이러한 문제를 해결할려고 시도합니다.

JVM 세이프포인트#

핫스팟 병렬 수집기에서 STW 가비지 수집을 실행하려면 애플리케이션 스레드를 모두 중단시켜야합니다. JVM은 사실 완전히 선제적인 멀티스레드 환경이 아닙니다. OS 코어 기능처럼 JVM도 조정 작업이 필요합니다. 애플리케이션 스레드마다 세이프포인트(안전점, safe point)라는 특별한 지점을 둡니다. 세이프포인트는 스레드의 내부 자료 구조가 보이는 지점이며, 이때 어떤 작업을 하기 위해 스레드는 잠시 중단될 수 있습니다.

JVM은 다음 두가지 규칙에 따라 세이프포인트를 처리합니다.

  • JVM은 강제로 스레드를 세이프포인트 상태로 바꿀 수 없습니다.
  • JVM은 스레드가 세이프포인트 상태에서 벗어나지 못하게 할 수 있습니다.

따라서 세이프포인트 요청을 받았을 때 그 지점에서 스레드가 제어권을 반납하게 만드는 코드가 VM 인터프리터 구현체 어디에 잇어야합니다. 세이프포인트 상태로 바뀌는 경우는 다음과 같습니다.

  • JVM이 전역을 세이프포인트 시간 플래그를 세팅합니다.
  • 각 애플리케이션 스레드는 폴링을 하면서 이 플래그가 세팅됐는지 확인합니다.
  • 애플리케이션 스레드는 일단 멈췄다가 다시 깨어날 때까지 대기합니다.

세이프포인트 시간 플래그를 세팅하면 모든 애플리케이션 스레드는 반드시 멈춰야합니다. 일반 애플리케이션 싀레드는 이런 식으로 풀링합니다.

다음의 경우, 스레드는 자동으로 세이프포인트 상태가 됩니다.

  • 모니터에서 차단되는 경우
  • JNI 코드를 실행하는 경우

다음의 경우, 스레드가 꼭 세이프포인트 상태가 되는 것은 아닙니다.

  • 바이트코드를 실행하는 도중(인터프리티드 모드)
  • OS가 인터럽트를 거는 경우

삼색 마킹#

삼색 마킹 알고리즘은 다음 순으로 진행됩니다.

  • GC 루트를 흰색 표시합니다.
  • 다른 객체는 모두 흰색 표시합니다.
  • 마킹 스레드가 회색 노드로 랜덤하게 이동합니다.
  • 이동한 노드를 검은색 표시하고 이 노드가 가리키는 모든 흰색 노드를 회색 표시합니다.
  • 회색 노드가 하나도 남지 않을 때까지 위 과정을 되풀이합니다.
  • 검은색 객체는 모두 접근 가능한 것이므로 살아남습니다.
  • 흰색 노드는 더 이상 접근 불가한 객체이므로 수집 대상이 됩니다.

image

동시 수집은 SATB라는 기법을 활용합니다. 즉, 수집 사이클을 시작할 때 접근 가능하나 그 이후에 할당된 객체를 라이브 객체로 간주합니다. 다만 변경자 스레드가 수집하는 도중에는 검은색 상태, 안하는 동안에는 흰색 상태와 같은 단점이 있습니다. 이를 방지하기 위해서 업데이트 시 쓰기 금지 등을 사용할 수도 있습니다.


CMS#

CMS 수집기는 중단 시간을 아주 짧게 하려고 설계된 테뉴어드(올드) 공간 전용 수집기 입니다. 보통 Parallel GC와 함께 사용합니다.

CMS는 중단 시간을 최소화하기 위해 애플리케이션 스레드 실행 중에 가급적 많은 일을 합니다. 따라서 일부 수행 단계는 좀 더 복잡합니다.

  • 초기 마킹(STW)
  • 동시 마킹
  • 동시 사전 정리
  • 재마킹(STW)
  • 동시 스위프
  • 동시 리셋

초기 마킹을 통해서 확실한 GC 출발점을 찾습니다. 그렇기 때문에 가능한 STW 시간을 줄입니다. 그렇기에 다음의 장단점을 가집니다.

  • 애플리케이션 스레드가 오랫동안 멈추지않습니다.
  • 단일 풀 GC 사이클 시간이 더 깁니다.
  • CMS GC 사이클이 살행되는 동안, 애플리케이션 처리율은 감소합니다.
  • GC가 객체를 추적해야 하므로 메모리를 더 많이 사용합니다.
  • GC 수행에 훨씬 더 많은 CPU 시간이 필요합니다.
  • CMS는 힙을 압착하지 않으므로 테뉴어드 영역은 단편화 될 수 있습니다.

이처럼 여러 장단점을 가집니다.

CMS 작동 원리#

CMS는 대부분 애플리케이션 스레드와 동시에 작동합니다. 그렇기 때문에 객체에 할당해서 사용하는데, 이러던 와중에 에덴 공간이 꽉 차버리면 어쩔 수 없이 중단이 되고, 이 경우는 병렬 수집기의 영 GC보다 느려집니다. 그렇기 때문에 CMS는 조금 다른 영 수집기를 사용합니다.

image

다음은 할당압이 너무 높아 CMF(동시 모드 실패, concurrent mode failure)이라고 하며, JVM은 어쩔 수 없이 풀 STW를 유발하는 ParallelOld GC 수집 방식으로 돌아갑니다(유일한 방법). 그렇기 때문에 CMF를 방지하기 위해서 CMS를 사용하는 경우에는 튜징 자체를 많이하게 됩니다.

CMS 기본 JVM 플래그#

CMS는 일반적으로 플래그 가짓수가 엄청 많으며, 이를 실행하는 일종의 플래그가 존재합니다.

-XX:+UseConcMarkSweepGC


G1#

가비지 우선은 병렬 수집기나 CMS와는 전혀 다른 스타일의 수집기입니다. 다음의 특징이 있습니다.

  • CMS보다 튜닝하기 쉽습니다.
  • 조기 승격에 덜 취약합니다.
  • 대용량 힙에서 확장성(특히, 중단 시간)이 우수합니다.
  • 풀 STW 수집을 없앨 수 있습니다.

G1 힙 레이아웃 및 영역#

G1 힙은 영역(리전)으로 구성됩니다.

image

  • 영역 크기 : <힙 크기> / 2048
  • 영역 개수 : <힙 크기> / <영역 크기>

G1 알고리즘 설계#

G1수집기는 다음의 역할을 수행합니다.

  • 동시 마킹 단계를 이용합니다.
  • 방출 수집기입니다.
  • 통계적으로 압착합니다.

G1 수집기는 워밍업을 하는 동안, GC 사이클이 한번 돌때마다 많은 '일반' 영역에서 가비지를 수집할 수 있는지 그 수치를 보관합니다.

G1 수집기는 TLAB 할당이나 서바이버 공간으로 방출, 테뉴어드 영역으로의 승격 등은 앞서 나온 다른 핫스팟 수집기와 대동소이하지만, 세대를 구성하는 영역이 연속되지 않는다는 단점이 있습니다.

G1 수집기에도 기억 세트(RSet, remembered set)라는 비슷한 장치로 영역을 추적합니다. RSet은 영역별로 하나씩, 외부에서 힙 영역 내부를 참조하는 레퍼런스를 관리하기 위한 장치입니다.

G1 단계#

G1의 수집단계는 다음과 같습니다.

  • 초기 마킹
  • 동시 루트 탐색
  • 동시 마킹
  • 재마킹
  • 정리

기본 JVM 플래그#

  • +XX:UseG1GC

G1의 주목표는 중단 시간 단축입니다. 따라서 최대 중단 시간을 개발자가 선정할 수 있는데, 값을 너무 잡게 잡으면 GC 서브시스템이 목표에 맞추지 못할 것입니다.

결론적으로는 8u40부터 사용하는 것이 지향되며, 저지연 워크로드에서는 아직 G1은 CMS 중단 시간보다 긴 편이라 아직까지는 긴편입니다. 다만 여전히 개선되고 있습니다.


셰난도아#

레드햇에서는 OpenJDK 프로젝트 일환으로 셰난도아라는 자체 수집기를 제작했습니다. 주 목표는 (대용량 힙) 중단 시간 단축입니다. 단계는 다음과 같습니다.

  • 초기 마킹(STW)
  • 동시 마킹
  • 최종 마킹(STW)
  • 동시 압착

일부 CMS, G1 단계와 비슷해보이지만 큰 차이점이 있습니다.

  • 브록스 포인터
    • 객체당 메모리 워드를 하나 더 써서 이전 가비지 수집 단계에서 객체가 재배치됐는지 여부를 표시하고 새버전 콘텐츠의 위치를 가리킵니다.

image

동시 압착#

GC 스레드는 다음과 같이 방출합니다.

  • 객체를 TLAB로 복사합니다.
  • CAS로 브룩스 포인터가 추측성 사본을 가리키도록 수정합니다.
  • 이 작업이 성공하면 압착 스레드가 승리한 것으로, 이후 이 버전의 객체는 모두 브룩스 포인터를 경유해서 액세스하게 됩니다.
  • 이 작업이 실패하면 압착 스레드가 실패한 것으로, 추측성 사본을 원상보구하고 승리한 스레드가 남긴 브룩스 포인터를 따라갑니다.

셰난도아 얻기#

일반적으로는 구하기 힘들고, 신경을 써야합니다.

  • -XX:+UseShenandoahGC

C4(아줄 징)#

Zulu는 다중 플랫폼에서 사용 가능한 OpenJDK 기반의 FOSS 솔루션입니다. 징(Jing)은 리눅스에서만 쓸 수 있는 상용 플랫폼이며 OpenJDK에 있는 자바 클래스 라이브러리를 사용하며완전히 다른 가상 머신입니다.

애플리케이션 스레드에 의해 재배치됐을지 모를 객체의 레퍼런스를 로드할 가능성이 있으므로 브룩스 포인터를 통해 새 위치를 추적합니다. 로드값 배리어는 이런 패턴을 지양하고 로드한 레퍼런스 각자의 로딩이 끝나면 현재 객체 위치를 직접가리키게 하는데 이를 자가 치유 배리어라고 부릅니다.

전체 GC 단계는 다음과 같습니다.

  • 마킹
  • 재배치
  • 재매킹

C4는 교대 압착이라는 기술로 연속적으로 압착합니다. 평상시 가상 메모리 서브시스템은 프로세스 주소 공간에 가상 페이지와 하부 물리 페이지 사이의 매핑 정보를 관리합니다.


밸런스트(IBM J9)#

IBM이 제작한 JVM. 다음의 목표를 가집니다

  • 대용량 자바 힙에서 중단 시간이 길어지는 현상이 있습니다.
  • 중단 시간이 최악인 경우를 최소화합니다.
  • 불균일 기억 장치 액세스(NUMA< Non-Uniform Memory Access>) 성능을 인지하여 활용합니다.

에덴이 꽉 차면 수집이 일어나며 이를 부분 가비지 수집(Partial Garbage Collection)이라고 합니다.

(너무 deep해서 생략...)


레거시 핫스팟 수집기#

여러가지 수집기가 있으나 가급적 사용하지 않는 것이 좋습니다.

  • Serial 및 Serial Old
  • 증분 CMS
  • 이프리케이트되어 사라진 GC 조합
  • 엡실론

결론#

가바지 수집기는 플랫폼의 강력한 장점이지만, 이를 잘알고 수집기 별로 고려해야하는 트레이드 오프를 아는 것이 중요합니다.

Last updated on