자바 기초 지식 - GC

KH·2023년 10월 18일

Java

목록 보기
11/11

Garbage Collection이란?

  • GC는 메모리 관리 기법 중 하나로, 동적으로 할당했던 메모리 영역 중 필요 없게 된 영역을 해제하는 기능이다.
  • 보통 프로그램을 개발하면 더 이상 사용하지 않은 메모리가 발생한다.
    • C언어에서는 이를 free() 로 참조를 해제하지만, Java는 GC가 알아서 이를 해결해준다
  • 가장 간단하게 구현할 수 있는 방법은 JVM의 Heap 메모리에서 모든 객체를 돌면서, 더 이상 사용되지 않는 객체인지 판단하고 해제하는것이다.
    • GC는 모든 Garbage를 수집해야하는 동시에, 살아있는 객체는 절대로 수집해서는 안됩니다.
      • 만약, 수집한다면 Segmentaion Fault가 발생

Root Space

  • 스택 변수, 전역 변수 등 heap 영역 참조를 담은 변수
  • 노란색 영역

GC 알고리즘

Reference Counting

  • Heap 영역에 선언된 객체들이 각각 reference count 라는 별도 숫자를 가지고 있다.
  • reference count는 몇가지 방법으로 해당 객체에 접근할 수 있는지를 뜻한다.
  • 해당 객체에 접근할 수 있는 방법이 없다 = reference count : 0 → GC 대상
  • 해당 방식에는 순환 참조 문제가 있다.
  • 그림 속 Root Space에서 모든 Heap Space의 참조를 끊는다고 가정하자.
  • 그러면 노란색 고리 안의 객체는 서로가 서로를 참조하고 있기 때문에 reference count가 1로 유지된다.
  • 결국 사용하지 않는 메모리 영역이 해제되지 못하고 메모리 누수가 발생하는 것이다.

Mark And Sweep

  • Mark And Sweep은 Reference Counting의 순환 참조 문제를 해결 가능
  • Root Space부터 해당 객체에 접근 가능한지, 아닌지를 메모리 해제의 기준으로 삼는다.
  • Root Space부터 연결된 객체를 찾아내고(Mark) 연결이 끊어진 객체는 지운다.(Sweep)
  • Root Space부터 연결된 객체 Reachable, 연결되지 않은 객체 Unreachable
  • 오른쪽 그림이 메모리 파편화를 방지하는 Compaction 과정이다.
    • Sweep 과정에서 삭제된 메모리의 Fragment들을 채워주는 역할을 한다.
    • Compaction이 필수 과정은 아니다.
  • GC를 실행하기 위해서는 GC를 실행하는 Thread를 제외한 나머지 Thread는 모두 작업을 멈춥니다.
    • 이후 GC 작업이 끝난뒤에 나머지 Thread가 다시 동작하게 됩니다.
    • 이때 발생하는 시간이 Stop the world
  • Stop the world시간을 최적화 하는것이 GC 튜닝의 목표입니다.

Heap

  • JVM의 Heap영역은 처음 설계될 때 2가지를 전제(Weak Generational Hypothesis)로 설계
    • 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
    • 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
  • 즉, 객체는 대부분 일회성이며, 메모리에 오랫동안 남아있는 경우는 드물다.
  • 따라서 객체의 생존 기간에 따라 Heap 영역을 Young, Old 총 2가지 영역으로 나누었다.
    • Young Generation 안에서 최대한 메모리를 해제하도록 설계
    • 초기에는 Perm 영역이 존재하였지만 Java8부터 제거되었다.
  • Young 영역(Young Generation)
    • 새롭게 생성된 객체가 할당(Allocation)되는 영역
    • 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
    • Young 영역에 대한 가비지 컬렉션(Garbage Collection)을 Minor GC라고 부른다.
  • Old 영역(Old Generation)
    • Young영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
    • Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
    • Old 영역에 대한 가비지 컬렉션(Garbage Collection)을 Major GC라고 부른다.

💡 Old 영역이 Young 영역보다 크게 할당되는 이유는 Young 영역의 수명이 짧은 객체들은 큰 공간을 필요로 하지 않으며 큰 객체들은 Young 영역이 아니라 바로 Old 영역에 할당되기 때문

Young generation

  • Eden, Survivor0, Survivor1 영역으로 나뉜다.
  • Eden 영역: 새로 생성된 객체가 할당(Allocation)되는 영역
  • Survivor 영역: 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역
  • Eden은 새롭게 생성된 객체들이 할당되는 영역
    • Eden영역이 꽉차면 minor gc가 발생
    • 사용하지 않는 메모리는 해제되고 해당 영역에 존재하는 객체는 Survivor 영역으로 이동
  1. 새로 생성된 객체가 Eden 영역에 할당된다.

  2. 객체가 계속 생성되어 Eden 영역이 꽉차게 되고 Minor GC가 실행된다.

    1. Eden 영역에서 사용되지 않는 객체의 메모리가 해제된다.

    2. Eden 영역에서 살아남은 객체는 1개의 Survivor 영역으로 이동된다.

  3. 1~2번의 과정이 반복되다가 Survivor 영역이 가득 차게 되면 Survivor 영역의 살아남은 객체를 다른 Survivor 영역으로 이동시킨다.(1개의 Survivor 영역은 반드시 빈 상태가 된다.)

  4. 이러한 과정을 반복하여 계속해서 살아남은 객체는 Old 영역으로 이동(Promotion)된다.

Age bit

  • GC는 객체가 얼마나 오래 살아남아있는지를 알기 위해서 살아남을 때 마다 Age bit를 1씩 증가하게 됩니다.
    • Eden → Suvivor로 갈때 증가합니다.
    • Suvivor → Suvivor로 이동할 때도 증가를 합니다.
  • 기본설정으로는 Age-bit이 15가 될 경우에는 Old Generation으로 이동하게 됩니다.
    • 이 숫자는 따로 변경을 할 수 있습니다.
  • 또한, 각 영역으로 이동하는것을 Promotion이라고 부릅니다

Minor GC

  • Minor GC는 실행시점이, Eden이 꽉찰경우 실행되며 이때 Young generation 영역을 모두 Mark And Sweep 합니다.
  • 속도가 빨라 Application에 크게 영향을 주지 않습니다.
  • 보통 0.5초에서 1초 사이에 끝난다.

Old Generation

  • 오래 살아남은 객체는 Old Generation으로 이동하게 됩니다.
    • 이 영역에서 객체의 메모리를 해제하는 것을 Major GC 라고 합니다.

Major GC

  • Major GC는 객체들이 계속 Promotion되어 Old 영역의 메모리가 부족해지면 발생
  • Old 영역은 Young 영역보다 크며 Young 영역을 참조할 수도 있다.
  • Major GC는 일반적으로 Minor GC보다 시간이 오래걸리며, 10배 이상의 시간을 사용한다.
  • 참고로, Major와 Minor를 모두 돌리는 것을 Full GC라고 합니다

Serial GC

  • 말 그대로 Serial 하게 하나의 Thread가 GC작업을 처리하는것을 말합니다.
  • Serial GC의 Young 영역은 앞서 설명한 알고리즘(Mark Sweep)대로 수행된다.
  • 하지만 Old 영역에서는 Mark Sweep Compact 알고리즘이 사용된다.
    • Compact는 Heap 영역을 정리하기 위한 단계로 유요한 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 존재하지 않는 부분으로 나누는 것이다.
  • Core가 1개인 경우에 사용을 하는경우가 있지만, 현재는 거의 쓰이지 않는다.

Parallel GC

  • 이 또한, Mark And Sweep And Compact 방식으로 동작하는 GC입니다.
    • 여러개의 Thread로 GC가 동작한다는 장점이 있습니다.
    • 설정에 따라서 MajorMinor에 대해서 Single Thread로 돌릴지 결정을 할 수 있습니다.
    • 일반적인 Parallel GC는 minor gc에 대해서만 멀티 스레딩을 수행하고, major gc는 싱글 스레딩으로 수행한다.
  • 멀티 코어 환경에서 애플리케이션 처리 속도를 향상시키기 위해 사용된다.
  • CPU의 Core와 메모리가 충분할때 유리한 방법으로 Throughput GC라고도 불린다.

Parallel Old GC

  • Parallel Old GC는 Parallel GC의 업그레이드된 버전이다.
  • major gc도 멀티 스레딩으로 수행하고 기존 Mark Sweep Compation의 개선 버전인 Mark Summary Compaction을 사용한다.
  • Java 8의 디폴트 버전이다.

Concurrent Mark-Sweep GC (CMS)

  • CMS GC는 애플리케이션의 지연 시간을 최소화 하기 위해 고안되었다.
  • 가비지 수집 작업을 애플리케이션 스레드와 동시에 수행하여, Stop The World 시간을 최소화
  • 응답이 느려질 순 있지만 응답이 멈추지는 않게 된다.
  • 하지만 메모리와 CPU를 많이 사용하고, Mark And Sweep 과정 이후 메모리 파편화를 해결하는 Compaction이 기본적으로 제공되지 않는다는 단점이 있다.
  • 이 때문에 시스템이 장기적으로 운영되다가 조각난 메모리들이 많아 Compaction 단계가 수행되면 오히려 Stop The World 시간이 길어지는 문제가 발생할 수 있다.
  • CMS GC는 Java 9 버전부터 deprecated되었고 Java 14 버전부터는 사용이 중단되었다.
  • Initial Mark: 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는다.
  • Concurrent Mark: 위에서 살아 있다고 확인한 객체에서 참조되어 있는 객체를 확인한다.
  • Remark: 위 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다.
  • Concurrent Sweep: 쓰레기를 정리한다.

Garbage First GC (G1)

  • G1(Garbage First) GC는 장기적으로 많은 문제를 일으킬 수 있는 CMS GC를 대체하기 위해 개발되었고, Java7부터 지원되기 시작하였다.
  • G1 GC는 기본 Heap을 Young generation, Old Generation등으로 나누는것은 동일하지만 공간을 region으로 나눠서 동작합니다
  • Heap을 일정 크기의 Region으로 잘게 나누어 어떤 영역은 Young Generation, 어떤 영역은 Old Generation으로 사용한다.
  • 런타임에 따라 G1 GC가 필요에 따라 영역 별 Region 개수를 튜닝함으로써 Stop The World를 최소화할 수 있다.
  • Java 9이상부터는 G1 GC를 기본 GC 실행 방식으로 사용한다.
  • G1은 Garbage First의 약어로 Garbage만 있는 Region을 먼저 회수한다고 해서 붙여진 이름이다.
  • 빈 공간 확보를 더 빨리 한다는 것은 조기 승격이나 급격히 할당률이 늘어나는 것을 방지하여 Old Generation을 비교적 한가하게 만들 수 있다.
  • 장점
    • 별도의 STW 없이도 여유 메모리 공간을 압축하는 기능을 제공한다.
    • 또한, 전체 Old Generation 혹은 Young Generation 통째로 Compaction을 할 필요 없고, 해당 Generation의 일부분 Region에 대해서만 Compaction을 하면 된다.
    • Heap 크기가 클수록 잘 동작한다.
    • CMS의 비해 개선된 알고리즘을 사용하고, 처리 속도가 더 빠르다.
    • Garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로 GC 빈도가 줄어든다.
  • 단점
    • 공간 부족 상태를 조심해야 한다. (Minor GC, Major GC 수행하고 나서도 여유 공간이 부족한 경우)
      • 이때는 Full GC가 발생하는데, 이 GC는 Single Thread로 동작한다.
      • Full GC는 heap 전반적으로 GC가 발생하는 것을 뜻한다.
    • 작은 Heap 공간을 가지는 Application에서는 제 성능을 발휘하지 못하고 Full GC가 발생한다.
    • Humonogous 영역은 제대로 최적화되지 않으므로 해당 영역이 많으면 성능이 떨어진다.

    출처:
    https://mangkyu.tistory.com/118
    https://steady-coding.tistory.com/590

0개의 댓글