자바의 GC

Date:     Updated:

카테고리:

태그:

Gabage Collection

JVM의 Heap 영역에서 사용하지 않는 객체를 삭제하는 프로세스 사용되는 객체인지를 판변하는 기준은 reachable이다.

GC Root

스택 변수, 전역 변수 등 heap 영역 참조를 담은 변수를 의미 GC Root 부터 시작하여 객체를 탐색하여 reachable을 판단한다.

GC Root의 종류

  • Class Loader에 의해 로딩된 클래스
  • 지역 변수와, 매개 변수
  • 현재 활성화된 스레드
  • 정적 변수
  • JNI Reference
    • JNI 메서드의 지역 변수 / 매개 변수
    • 전역 JNI 참조 변수
  • 모니터로 사용된 객체

GC의 동작 방식

GC가 발생했다면 다음의 공통적인 단계를 따르게 된다.

  1. Stop The World
  2. Mark and Sweep

Stop the World

GC를 실행하기 위해 JVM이 어플리케이션 실행을 멈추는 것이다. 이말은 GC를 실행하는 스레드를 제외한 모든 스레드들의 작업 중단되고, GC가 완료되면 작업이 재개된다.

Stop the World가 실행되면 왜 어플리케이션 실행이 멈출까?

어플리케이션 스레드가 멈추어야 현재 메모리 상에서 살아있는 개체를 식별할 수 있기 때문이다. 만약 어플리케이션 스레드를 멈추지 않는다면, 어플리케이션의 동작에 따라 변화하는 객체들의 상태를 빠르게 반영하지 못할 수도 있다.

객체의 상태를 빠르게 반영하지 못하는게 왜 문제일까?

어플리케이션 스레드를 멈추지 않고 Marking을 하게된다면, 스레드가 하는 일에 따라 Marking은 영원히 끝나지 않을 수 있다. (객체간의 참조는 계속 변할 수 있으므로)

또한, reachalbe한 객체이지만, Mark and Sweap 단계에 marking 되지 못하여 버그가 발생할 수 있다.

Mark and Sweep

Mark : 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업 Sweep : Mark 단계에서 사용되지 않음으로 식별된 메모리를 해제하는 작업

사용 유무의 기준은 객체의 reachalbe

Compact

Marking된 객체들을 메모리 영역의 처음부터 몰아넣으로써 메모리 단편화를 제거한다.
이는 실제 객체를 복사하고 이 객체들의 참조 정보를 업데이트하는 일을 수반한게 된다.

Trade-off

Compact를 함으로써, Stop-the-World의 시간이 길어진다. 하지만, 메모리 단편화 제거 및 연속적인 공간에서 객체를 생성하는 것을 가능케 해준다.

Minor GC

Eden 영역이 꽉 차면 Minor GC가 발생한다. 사용되지 않는 메모리는 해제되고, 해제되지 않는 객체는 Survivor 영역으로 이동한다.

Survivor 영역이 꽉 차면 Minor GC가 발생한다. Survivor 영역이 가득 차게 되면, Minor GC가 발생하고 살아남은 객체를 다른 Survivor 영역으로 이동시킨다. (1개의 Survivor 영역은 반드시 빈 상태가 된다.)

Survivor 영역

Suvivor 영역은 2개 존재한다.

일정 횟수의 Minor GC에서 살아남은 객체는 Old 영역으로 이동한다 (Promotion).

GC로부터 살아남은 횟수는 Object Header에 저장되어 있는 age에 저장된다.

용어 정리

Eden : 객체가 처음 생성되면 이곳에 저장된다. Survivor : Eden에 존재하는 객체가 Minor GC에서 살아남을 때, 이곳으로 이동한다. Promotion : 객체가 Young 영역에서 Old 영역으로 이동되는 것을 의미한다.

Major GC

객체들이 계속 promotion되어 Old 영역의 메모리가 부족해지면 Major GC가 발생한다.

Young 영역과 Old 영역을 동시에 처리하는 GC를 Full GC라고 한다.

Gabage Collection의 종류

  1. Serial GC
  2. Parallel GC
  3. Parallel Old GC
  4. CMS (Concurrent Mark Sweep) GC
  5. G1 (Garbage First) GC

Serial GC

  • 하나의 스레드로 GC를 실행하기 때문에, 멀티 코어 CPU의 이점을 제대로 살리지 못한다.
  • Mark and Sweep 알고리즘에 더해 Compact 작업이 추가
  • Stop The World 시간이 길다
    • 하나의 스레드로 작업하기 때문에
  • 싱글 쓰레드 환경 및 Heap이 매우 작을 때 사용

Compact

Heap 영역을 정리하기 위한 단계로 유효한 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 존재하지 않는 부분으로 나누는 작업

Paralle GC

  • 여러 개의 쓰레드로 GC를 실행
  • 멀티코어 환경에서 사용
  • Java 8의 default GC방식

다수의 쓰레드를 이용하니까 동시성 이슈는 어떻게 해결할까

동기성 이슈를 회피하기 위해 PLAB(Parallel Location Allocation Buffer)를 사용한다.

CMS GC

  • Stop The World 최소화를 위해 고안
  • G1 GC 등장에 따라 Deprecated
  • Compaction이 기본적으로 제공되지 않는다는 단점 존재

Old 영역에 대해서 Compaction을 수행하지 않고 객체를 할당할 수 있는 공간을 관리하는 자료구조(free-list)를 따로 관리한다.

Mark와 Sweep과정을 특정 단계 빼고는 어플리케이션 스레드와 병렬적으로 수행한다.

궁금한 점

CMS GC의 마지막 단계, Concurrent Sweep 도중에 새로운 객체를 만들거나 객체 그래프가 변경됬을 때, reachable 객체이지만 marking이 되지 않아서 Sweep의 대상이 될 수 있지 않나?

G1 GC

  • Heap을 Region으로 나누어 사용
  • Region은 Young Gereration과 Old Generation으로 나뉨
  • 런타임 때 GC가 필요에 따라 영역별 Region 개수 튜닝 -> Stop The World를 최소화 할 수 있음 (왜?)
  • Java 9부터 default GC방식

앞에서의 GC는 물리적으로 Young 영여과 Old 영역이 분리되어 있다. G1 GC는 Eden에 객체를 할당하고, Survivor로의 카피하는 등의 작업을 사용하지만 물리적으로 메모리 공간을 나누지 않는다. 대신 Region이라는 개념을 새로 도입하여 Heap을 균등하게 여러 개의 지역으로 나누고, 각 지역을 역할과 함께 논리적으로 구분하여 객체를 할당한다.

G1 GC는 Eden, Survivor, Old 역할에 더해 Humongous와 Available/Unused라는 두 가지 역할을 추가하였다.

  • Humonogous : Region 크기의 50%를 초과하는 객체를 저장하는 Region
  • Available/Unused : 사용되지 않는 Region

G1 GC은 Heap을 동일한 크기의 Region으로 나누고, collection set이라 불리는 것으로 분류하여, 분류된 region들만을 고려하여 GC를 수행한다.

collection set 중 가비지가 많은 Region에 대해 우선적으로 GC를 수행하는 것이다.

G1 GC의 Minor GC

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

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

Marking을 하지않고 어떻게 가비지가 가장 많은 지역을 찾을 수 있는 걸까?

GC 스레드와 애플리케이션 스레드와 동시에 수행되는 concurrent phase에 cuncurrent Marking을 통해 각 region에 살아있는 객체에 대한 mark를 시도한다. (비록 객체 그래프가 변하더라도 말이다.)
이 정보는 각 region의 살아있는 정보를 유지할 수 있게 하고 Collection Set을 만드는데 참조된다.

G1 GC의 Major GC

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

Gabage Collection (추가 공부)

TLAB (Thread Local Allocation Buffer)

객체를 새로 생설할 때, 이 객체는 Eden 영역에 저장된다. 이때 어느 위치에 객체를 저장할 것인지를 고려해야한다.

TLAB는 객체를 Eden의 어느 위치에 저장할 것인지를 스레드마다 저장하는 방식으로 동작한다.

이로인한, 객체를 동시에 생성할 때 발생할 수 있는 객체 할당의 동시성 이슈를 필할 수 있다.

TLAB의 Trade-off

TLAB 영역을 새로 할당받거나, 할당된 TLAB가 부족하여 새로이 할당 받을 때는 동기화 이슈가 발생한다. 하지만 객체 할당에 비해서는 동기화 이슈가 대폭 줄어든다.

Card Table

GC에서 Young 영역과 Old 영역을 나눈 이유는 영역을 나누어, 사용되지 않는 객체를 지우는 영역을 나눔으로써 성능 향상을 노리기 때문이다.

하지만, 특정 객체가 서로 다른 영역(Young, Old)의 객체를 참조하고 있을 때 문제가 발생한다. 기껏 전체 메모리 공간을 Young 영역과 Old 영역으로 나누어 Young 영역에 대해서만 청소를 함으로써 GC에 걸리는 시간을 줄여보려고 했더니, Old 영역도 검사를 해야하기 때문이다.

JVM은 위의 문제를 Card Table을 활용한 Card Marking을 통해 해결한다.

만약 Young 영역의 객체를 참조하는 객체가 Old 영역에 있다면, 이 Old 영역의 객체의 시작주소에 카드(일종의 Flag)를 Dirty로 표시하고 해당 내용을 Card Table에 기록하여, 객체 간의 참조 관계를 쉽게 파악할 수 있도록 했다.

JVM은 Minor GC 수행시 Card Table의 Dirty Card만 검색한다면, Old 영역으로 부터 참조되는 Young 영역의 살아 있는 개체를 식별할 수 있으므로, Old 영역을 검사하지 않고서도 빠르게 Mark 단계를 끝낼 수 있다.

질문

그럼 Old 영역에서 Young 영역의 참조는 Card Table로 가능 그렇다면 Young 영역에서 Old 영역으로의 참조는?
Young 영역을 탐색하는 건가?

PLAB(Parallel Location Allocation Buffer) - 공부 더 필요

여러 스레드를 사용하여 GC를 할 때 발생할 수 있는 동시성 이슈를 회피하기 위해 PLAB를 사용할 수 있다.

PLAB는 각 GC 스레드가 받는 Old 영역의 객체 할당 공간이다. 각 GC 스레드는 이를 통해 스레드 경합하지 않고 바로 Old 영역에 객체를 이동시킬 수 있다.

The JVM Write Barrier - Card Marking

참조 필드에 객체가 저장된 메모리 주소를 저장함으로써, 개발자는 객체를 참조할 수 있다.

이론적으로 참조 필드를 작성하는 것은 동일한 크기의 원시 값을 작성하는 것과 같지만, 실제로는 GC를 지원하기 위해 일부 계산이 이루어진다.

위의 Dirty Cards에 dirty 체크하는 것이 Card Marking이다.

출처
http://psy-lob-saw.blogspot.com/2014/10/the-jvm-write-barrier-card-marking.html https://d2.naver.com/helloworld/1329 https://mangkyu.tistory.com/119 https://dhsim86.github.io/java/2018/02/04/what_is_garbage_collection-post.html https://dhsim86.github.io/java/2018/02/05/gc_algorithms-post.html https://d2.naver.com/helloworld/1329 https://mangkyu.tistory.com/119 https://dhsim86.github.io/java/2018/02/04/what_is_garbage_collection-post.html https://dhsim86.github.io/java/2018/02/05/gc_algorithms-post.html

Java 카테고리 내 다른 글 보러가기

댓글 남기기