[Java] Garbage Collection

기면지·2021년 7월 23일
2

Java

목록 보기
1/1
post-thumbnail

안녕하세요!
오늘은 GC라고 많이 불리는 Garbage Collection 의 동작 방식에 대해 알아보겠습니다 !!


Stop-The-World (STW)

GC를 언급하면, Stop-The-World 라는 개념이 등장합니다.
STWGC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것을 의미합니다.

STW가 발생하면 GC를 실행할 쓰레드만 작업하고, 나머지 쓰레드는 작업을 멈춥니다.
GC를 완료해야만 멈췄던 쓰레드에서 다시 작업을 시작합니다.
어떤 GC 알고리즘을 사용해도 STW는 발생하며, 이 STW 시간을 줄이는 것을 GC튜닝 이라고 합니다.

Garbage Collector

Java는 프로그램 코드에서 명시적으로 메모리를 해제하지 않습니다.
GC는 더 이상 필요 없는 가비지(쓰레기) 객체를 찾아 지우는 작업을 수행합니다.

  • GC는 Heap 메모리에서 활동합니다.
  • JVM에서 GC의 스케줄링을 담당하고, 개발자가 직접 관여하지 않아도 더이상 사용하지 않는 점유된 메모리를 제거해주는 역할을 합니다.
  • JVM이 GC를 수행하는 시점은? 아무도 모릅니다!

GC의 전제 조건

Garbage Collectorweak generational hypothesis 라는 전제 조건 하에 만들어졌습니다.

  1. 대부분의 객체는 금방 접근 불가능 상태 (Unreachable) 가 된다.
  • 대부분의 객체는 중괄호({}) 안에서 생성됩니다.
  • 이 객체들은 괄호가 끝나는 시점에 더이상 사용되지 않습니다.
  • 특수한 경우에는 오래 사용될 수 있지만, 대부분의 경우 Unreachable한 상태가 되어 GC의 대상이 됩니다.
  1. 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
  • 일반적으로 순차적인 로직에 의해 객체를 생성하여 활용합니다.
  • 이 과정에서 앞으로 생성될 객체는 그 다음의 로직에서 사용된 이후 대부분 사용되지 않게 됩니다.
  • 그러한 특성으로 인해 더이상 참조되지 않는 객체에 대해 GC 처리할 수 있게 됩니다.

Heap에서 GC의 물리적 공간

위의 전제조건을 최대한 적용하기 위해서 HotSpot VM에서는 물리적 공간을 YoungOld 크게 2개로 나누었습니다.
*HotSpot: 데스크톱과 서버 컴퓨터를 위한 자바 가상 머신. 반복적으로 실행되는 핫스팟을 위한 프로그램 성능을 계속적으로 분석한 뒤 최적화를 목표로 한다. 성능에 덜 민감한 코드에 대해 부하를 최소화하여 고성능 실행을 목적으로 한다.

Young Generation

새롭게 생성한 객체의 대부분은 Young 에 위치합니다.
대부분의 객체가 금방 Unreachable 상태가 되기 때문에 많은 객체가 Young 영역에 생성되었다가 사라집니다.
이 영역에서 객체가 사라질 때 Minor GC 가 발생합니다.

Old Generation

Unreachable 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사됩니다.
대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생합니다.
이 영역에서 객체가 사라질 때 Major GC (Full GC) 가 발생합니다.

Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우


Old 영역에는 512 바이트의 chunk로 되어 있는 Card Table 이 존재합니다.
카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시됩니다.
Young 영역의 GC를 실행할 때에는 Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 GC 대상인지 식별합니다.
카드 테이블은 write barrier을 사용해 관리합니다.
(write barrier : Minor GC를 빠르게 할 수 있도록 하는 장치)

Young 영역

YoungEden, Survivor(2개) 총 3개의 영역으로 나뉩니다.

GC 플로우

  • 새로 생성한 대부분의 객체는 Eden 영역에 위치합니다.
  • Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동합니다.
  • Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓입니다.
  • 하나의 Survivor 영역이 가득 차면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동합니다.
  • 가득 찬 Survivor 영역은 아무 데이터도 없는 상태로 됩니다.
  • 위의 과정을 반복하다가 계속 살아남은 객체는 Old 영역으로 이동합니다.

여기서 알 수 있는 사실이 있습니다.
Survivor 영역 중 하나는 반드시 비어 있는 상태여야 합니다.
만약 두 Survivor 영역에 모두 데이터가 존재하거나, 두 영역 모두 사용량이 0이라면 정상적인 시스템 상황이 아닌 것입니다.

아래는 HotSpot VM에서 사용하는 메모리 할당 기술 두 가지입니다.

Bump-the-Pointer

Bump-the-Pointer 방식은 Eden 영역에 할당된 마지막 객체를 추적합니다. 이 마지막 객체는 Eden 영역의 top에 있습니다.
그 다음에 생성되어 Eden 에 할당되는 객체가 있으면, 해당 객체의 크기가 Eden 영역에 넣기 적당한지만 확인합니다.
해당 객체의 크기가 적당하다면 Eden 영역에 추가하고, top에 위치하게 됩니다.
따라서, 새로운 객체를 생성할 때 마지막에 추가된 객체만 점검하면 되므로 매우 빠른 메모리 할당이 이루어집니다.

TLABs (Thread-Local Allocation Buffers)

TLABs멀티 스레드 환경에서 Thread-Safe 하기 위해서 사용합니다.
여러 스레드에서 사용하는 객체를 Eden 영역에 저장하려면 Lock이 발생하고, Lock-Contention 때문에 성능이 떨어집니다. 이것을 해결한 방법이 TLABs 입니다.

TLABs 방식은 각 스레드가 각자의 몫에 해당하는 Eden 영역의 작은 부분을 가질 수 있도록 합니다.
각 스레드에는 자신의 TLAB 에만 접근할 수 있기 때문에, Bump-the-Pointer 라는 기술을 사용해도 Lock 없이 매모리 할당이 가능합니다.

Old 영역

Old 는 기본적으로 데이터가 가득 차면 GC를 실행합니다.
GC 방식에 따라 처리 절차가 달라집니다.

GC 방식

  • Serial GC
    적은 메모리와 CPU 코어 개수가 적을 때 적합하므로 운영 서버에서 절대 사용하면 안되는 방식입니다.
    데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식으로 SerialGC 를 사용하면 애플리케이션의 성능이 많이 떨어집니다.
    Mark-Sweep-Compact
    - Mark : 처음으로 Old 영역에서 살아 있는 객체를 식별합니다.
    - Sweep : 힙의 앞 부분부터 확인해 살아있는 객체만 남깁니다.
    - Compaction : 마지막으로 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눕니다.

  • Parallel GC (Throughput GC)
    Serial GC 와 기본적인 알고리즘은 동일합니다.
    다른 점은 Serial GC 의 처리 쓰레드는 하나이고, Parallel GC 의 처리 쓰레드는 여러 개 입니다.
    따라서 Serial GC 보다 빠르게 객체를 처리할 수 있습니다.
    메모리가 충분하고 코어의 개수가 많을 때 유리한 방식입니다.

  • Parallel Old GC (Parallel Compacting GC)
    앞서 설명한 Parallel GC 와 비교하여 Old 영역의 GC 알고리즘만 다릅니다.
    Mark-Summary-Compaction 단계를 거칩니다.
    Summary 단계는 앞서 GC를 수행한 영역에 대해 별도로 살아있는 객체를 식별한다는 점에서 Sweep 단계와 다르며, 약간 더 복잡합니다.

  • CMS GC (Concurrent-Mark-Sweep GC, Low Latency GC)
    STW 시간이 매우 짧기 때문에 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC 를 사용합니다.
    다른 GC 방식보다 메모리와 CPU를 더 많이 사용하고, Compaction 단게가 기본적으로 제공되지 않습니다.
    조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC의 STW 시간보다 STW 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 합니다.

    - initial Mark : 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾습니다. 멈추는 시간은 매우 짧습니다.
    - Concurrent Mark : 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가며 확인합니다. 이 처리는 다른 쓰레드가 실행 중인 상태에서 동시에 진행합니다.
    - Remark : Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인합니다.
    - Concurrent Sweep : 가비지(쓰레기)를 정리합니다. 이 처리 또한 다른 쓰레드가 진행 중인 상태에서 동시에 진행합니다.

  • G1 GC (Garbage First GC)
    앞의 과정에서 구분한 Young , Old 영역이 존재하지 않습니다.
    바둑판처럼 나눈 각 영역에 객체를 할당하고 GC를 실행합니다.
    영역이 꽉 차면 다른 영역에 객체를 할당하고 GC를 실행합니다.
    앞의 Young 의 세 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC 방식입니다.
    CMS GC 를 대체하기 위해 만들어졌으며, JAVA9 부터 Default GC 입니다.
    큰 장점은 성능이며 어떤 GC 방식보다 빠릅니다.
    대용량의 메모리가 있는 멀티 프로세서 시스템을 위한 GC 입니다.


마무리

오늘은 Java의 GC에 대해 알아봤습니다.
정리하면서도 내용이 많고 복잡해서.. 약 3일동안 자료를 수집하고 정리한 것 같습니다!
많은 도움이 되었으면 좋겠습니다 :)

참고자료

네이버 D2 Garbage Collection
위키백과 HotSpot VM
G1 GC

profile
𝙎𝙈𝘼𝙇𝙇 𝙎𝙏𝙀𝙋𝙎 𝙀𝙑𝙀𝙍𝙔 𝘿𝘼𝙔

0개의 댓글