[Java] Garbage Collection

Ahnick·2021년 2월 17일
0
post-thumbnail
post-custom-banner

본 포스팅은 네이버 D2 블로그의 GC 포스트를 개인적으로 정리한 내용입니다.
자세한 내용은 위 포스트를 참고해 주세요

Java Garbage Collection

자바 이전의 언어 C/C++는 사용자가 메모리 제어에 많은 권한을 가지며
개발자가 직접 인스턴스를 할당하고 할당을 해제해줘야 했습니다.

하지만 자바는, GC(Garbage Collection)이라는 개념을 도입하여 개발자의 메모리
제어 권한을 회수하는 대신 메모리를 JVM이 직접 관리하며 개발자의 개발 편의성을
증가시켜 주었습니다.

Garbage Collection 동작과정

Stop-The-World

STW는, GC를 실행하기 위해서 JVM이 실행을 일시정지한 상태를 말합니다.
이 상태에서는 모든 쓰레드가 정지하며 오직 GC를 실행하기 위한 쓰레드가 동작합니다
어떠한 GC 알고리즘을 사용하더라도 STW는 발생하며 대부분의 경우 이 시간을 줄여
성능을 향상시키는 일련의 과정을 GC 튜닝이라고 합니다.

Weak Generational 가설

GC는 두 가지의 전제 조건을 바탕으로 디자인되었습니다.

  • 대부분의 객체는 빠른 시간 내에 unreachable한 상태가 된다
  • 오래된 객체에서 젊은 객체로의 참조는 흔하지 않다

이러한 가설을 Weak Generational 가설이라고 하며 이 가설의 장점을 최대한 살리기 위해
HotSpot(자바의 가장 일반적인 JVM)에서는 자바의 Heap 메모리 영역을 크게 두 영역으로
분할합니다 ( Metaspace 제외 )

  • Young 영역
    새롭게 생성된 객체는 대부분 이 공간에 위치합니다. 위의 가설에 따라 대부분의 객체는
    금방 접근 불가능한 상태가 되므로 대부분의 객체는 이 공간에서 생성되고 사라집니다.
    이 영역에서의 GC 동작을 Minor GC라고 합니다.

  • Old 영역
    Young 영역에서 살아남은 객체들이 여기로 이동됩니다. 대부분의 경우 Young 영역보다
    큰 크기를 가지며, Old 영역에서는 GC가 Young 영역보다 적게 동작합니다.
    이 공간에서 GC가 동작할 때 Major GC (or Full GC)가 발생했다고 합니다.

  • Perm 영역 ( Metaspace )
    Java8 이전에는 Heap 메모리에 Perm 영역이라는 공간이 존재했지만,
    Java8이후에는 Metaspace라는 공간으로 대체되었습니다.
    Metaspace는 Heap에 포함되지 않고 Native Memory에 할당되지만
    Perm 영역이 이전에 Heap 영역에 존재했고 이곳에서 발생했던 GC도 Major GC로
    불렀기 때문에 추가적으로 작성했습니다.
    물론 Metaspace에서도 GC는 동작합니다! -> 클래스로더가 로드한 데이터를 정리

Young 영역에서의 GC 동작

Young 영역도 공간이 분할되는데, Young 영역은 3개의 공간으로 분할됩니다

  • Eden 영역
  • 2개의 Survivor 영역

이 두개의 공간에서 발생하는 동작과 특징들은 다음과 같습니다

  • 새로 생성한 대부분의 객체는 Eden 영역에 위치
  • Eden 영역에서 발생한 GC에서 살아남은 객체는 Survivor 영역으로 이동
  • 하나의 Survivor 영역이 가득 차면, 그 중에서 또 살아남은 객체를
    Survivor 영역으로 이동, 그 후 직전의 Survivor 영역은 비워진다
  • 이 과정을 반복하여 계속 살아남은 객체가 Old 영역으로 이동한다

이 알고리즘에서 두 개의 Survivor 영역 중 하나의 Survivor 영역은
항상 비워진 상태
입니다. 다음은 HotSpotVM에서 새로운 객체의 메모리 할당을
효율적으로 하기 위한 두 가지 기술입니다.

Bump-the-pointer

Bump-the-pointer는 Eden에서 마지막으로 할당단 객체의 포인터를 가지며
마지막 객체는 Eden영역의 꼭대기(top)에 존재합니다. 그리고 그 다음에 생성되는
객체가 존재하면 해당 객체의 크기가 Eden 영역에 들어갈 수 있는지를 확인합니다.
들어갈 공간이 충분하면 Eden 영역에 성공적으로 할당됩니다.
뭔가 C언어의 Implicit, Explicit 동적 할당이 생각나네요 😮

하지만 멀티 쓰레딩 환경에서 Thread-safe를 지키기 위해 lock을 사용한다면
Eden 영역에서 사용되는 객체때문에 성능이 매우 떨어지게 됩니다.
그렇게 나오게 된 기술이 TLABs 입니다.

TLABs

TLABs는 Thread-Local Allocation Buffers의 약어로, 각각의 쓰레드가
Eden 영역을 분할하여 가져가는 방식입니다. 각 쓰레드는 Eden 내에서
자신의 TLAB만 참조할 수 있기 때문에 Lock이 필요 없게 됩니다.

여러 가지 GC 알고리즘

GC에 대한 알고리즘

Old 영역은 일반적으로 데이터가 가득 찼을때 GC를 실행합니다.

Young&Old에서 사용하는 GC에는 여러가지 방식이 있는데 이를 살펴보면

  • Serial GC
  • Parallel GC
  • Parallel Old GC
  • CMS GC
  • G1 GC

5가지의 알고리즘이 있습니다.

Serial GC

Old 영역에서의 GC는 Mark-Sweep-Compact라는 알고리즘을 사용합니다.
먼저 Old 영역에서 살아있는 객체를 식별(Mark)하고, 앞 부분부터
살아있지 않은 객체를 제거(Sweep)합니다. 마지막은 제거된 객체의 의해
사이사이 생긴 빈 공간을 압축(Compaction)합니다.

Serial GC 방식은 메모리와 CPU 코어 개수가 적을 때 적합한 방식입니다.

Parallel GC ( Default at Java7 )

Parallel GC와 Serial GC의 알고리즘은 동일하지만 Serial GC는
GC를 처리하는 쓰레드가 하나지만 Parallel GC는 여러 개의 쓰레드가 같이 동작합니다.
따라서 메모리가 많고 코어의 개수가 많을 때 적합한 방식입니다.

Parallel Old GC

Parallel Old GC는 앞의 Parallel GC와 비교해 Old 영역에서 별도의 알고리즘이
사용되는 방식을 뜻합니다. 이 방식은 Sweep 대신 Summary 단계를
가진다는 특성이 있습니다.

CMS GC

CMS GC는 Concurrent Mark & Sweep GC의 약어로, Serial GC에서 몇 가지
단계를 추가한 방식입니다.

초기 단계에서 클래스 로더에 가장 가까운 객체 중 살아 있는 객체를 찾고
끝냅니다. 따라서 Stop-the-world 상태가 매우 짧습니다. 이후
Concurrent Mark 단계에서 방금 살아있다고 확인한 객체에서 참조하고 있는
객체들을 따라가면서 확인하며, 이 단계에서는 Stop-the-world를 하지 않고
다른 쓰레드들과 같이 동작합니다.

그 다음 Remark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인합니다.
마지막으로 Concurrent Sweep 단계에서 Garabage를 정리합니다.
이 작업 또한 다른 쓰레드가 실행되는 중에 진행되기 때문에
전체적으로 CMS GC방식은 Stop-The-World 시간이 굉장히 짧습니다.

애플리케이션의 응답 속도가 매우 중요할 때 CMS GC방식을 주로 사용합니다.
하지만 CMS GC방식은 지속적으로 쓰레드를 사용하므로
CPU의 자원을 많이 사용하며 Compaction 단계가 제공되지 않으므로
메모리 파편화가 발생할 확률이 높습니다. 하지만 별도의 Compaction을 실행하면
Stop-The-World 시간이 길어지므로 Compaction을 적절하게 사용해야합니다.

G1 GC ( Java 8 )

G1 GC는 Garbage First GC의 약어입니다. G1 GC는 이전의 Old, Young 영역의 구분을
따로 하지 않고
, heap 메모리를 하나의 바둑판 영역처럼 사용합니다.

Garabage Collection을 하는 동작 자체는 CMS GC 방식과 유사하며, CMS와 달리
Heap의 사용량이 특정 임계값을 넘어가면 실행됩니다.

reachable한 객체들은 GC동작 중 live 영역으로 이동하며, 가상 메모리 주소
이동하기 때문에 메모리 파편화가 발생하지 않습니다.

전체 Heap에 대해서 GC가 일어나지 않고, 일부에서만 GC가 동작하기 때문에
커다란 Heap 공간을 가질 경우 유리합니다.

Parallel GC와 CMS GC의 절충안으로 평가받아, 준수한 응답속도 및 메모리 효율을 보장합니다.

정리

지금까지 자바가 GC를 통해서 메모리를 정리한다는 사실은 알고 있었지만,
정확히 어떻게 동작하는지 어떤 알고리즘들이 있는지도 몰랐고
개발자가 GC를 직접 튜닝할 수 있다는것도 처음 알았네요 ㅎㅎ
역시 자바는 알면 알수록 깊이있는 언어인 것 같습니다.

post-custom-banner

0개의 댓글