Garbage Collector

김성호·2023년 1월 18일
0

Java

목록 보기
3/6
post-custom-banner

Garbage Collector란?

메모리 관리 기법 중 하나이다.
프로그램이 동적으로 할당했던 메모리 영역(Heap) 중 필요 없게 된 영역을 알아서 해제하는 기법이다.
JVM의 Heap영역에서 사용하지 않는 객체를 삭제하는 프로세스이다.
C, C++의 경우 Heap영역의 메모리를 관리하기 위해 코드레벨에서 할당받고, 해제해야 했다.
할당받은 메모리 영역을 제대로 해제하지 않아 Memory Leak문제가 발생할 수 도 있다.

GC의 장점과 단점

장점

  • 개발자의 실수로 인한 메모리 누수를 막을 수 있다.
  • 해제된 메모리에 접근하는 오류를 막을 수 있다.
  • 해제된 메모리를 또 해제하는 이중 해제를 막을 수 있다.

단점

  • 어떤 메모리 영역이 해제의 대상이 될지 검사하고 해제하는 일은 프로그램이 해야할 일을 못하도록 방해하는 순수한 오버헤드이다.
  • 개발자가 GC가 정확하게 언제 메모리를 해제하는지 알 수 없다. 실시간성이 강하게 강조되는 프로그램의 경우 GC에게 메모리 관리를 맡기는 것이 좋지 않을 수도 있다.

GC의 작동

GC root(=Root space)에서부터 각각 참조하고 있는 객체들을 하나씩 탐색해 나간다.

  • Root Space : 스택 변수, 전역 변수 등 Heap영역 참조를 담은 변수이다.

참조되는 객체들을 reachable하다 하고,
참조되고 있지 않은 객체들을 unreachable하다 한다.

GC root가 될 수 있는 것들

  • stack에 있는 지역변수나 파라미터들
    - stack : 메소드 호출을 스택 프레임이라는 블록으로 쌓으며 로컬 변수, 중간 연산 결과물이 저장되는 영역
  • 메소드 영역에 있는 변수들
    - method(=static) : 프로그램의 클래스 구조를 메타 데이터처럼 가지며 메소드들의 코드들이 저장되는 영역
  • Java native method stack에 있는 객체들
    - C, C++ 등의 low level code를 실행하는 stack

Heap영역

Heap영역은 Old Generation과 Young Generation으로 구분이 된다.

  • Young Generation : 새로운 객체들이 할당되는 영역
  • Old Generation : Young Generation에서 오랫동안 살아남은 객체들이 존재하는 영역
  • MetaSpace : Heap영역에 있는 건 아니다. GC시에 필요한 클래스, 메소드의 요약 정보가 존재하는 영역

스택영역, 메소드영역, native method stack, Heap영역에 있는 객체들에 의해 참조되고 있는 객체들은 reachable하다고 판단한다.

어느 곳의 객체에서도 참조되고 있지 않은 객체를 unreachable하다 하고 GC의 수거 대상이 된다.

GC의 알고리즘

1. Reference Counting

Heap영역에 선언된 객체들이 각각 reference count라는 별도의 숫자를 가지고 있다.
reference count는 몇 가지 방법으로 해당 객체에 접근할 수 있는지를 뜻한다.
reference count가 0이 되면(=해당 객체에 접근할 수 있는 방법이 없다) GC의 대상이 된다.

Reference Counting의 한계점

순환참조 문제
Root Space에서 Heap Space접근을 모두 끊게 되면 Heap Space안에서 서로가 서로를 참조하는 객체들이 남게 되고 이들이 점유하고 있는 사용하지 않는 메모리 영역이 해제되지 못하고 Memory Leak이 발생한다.

2. Mark and Sweep, Compact algorithm

  • Reference Counting의 순환 참조 문제를 해결할 수 있다.
  • Root Space에서 해당 객체로 접근이 가능한지를 해제의 기준으로 삼는다.

    Mark And Sweep의 특징

    • 의도적으로 GC를 실행시켜야 한다.
    • 어플리케이션 실행과 GC실행이 병행된다.

- Mark

Root Space 로부터 그래프 순회를 통해 모든 변수를 스캔하면서 각각 어떤 객체를 참고하고 있는지 마킹하는 과정이다.
즉, reachable한 객체와 unreachable한 객체를 식별하는 과정이다.

- Sweep

Unreachable한 객체들을 heap에서 제거하는 과정이다.

- Compact

알고리즘 과정에 따라서 추가되기도 하는 과정이다.
Sweep 후에 띄엄띄엄 남아있는 객체들을 한 곳에 모아서 메모리단편화를 막아주는 작업이다.

GC가 한번 일어날 때마다 Mark와 Sweep이 수행되고 어쩔 때는 compact과정까지 수행된다.

Young Generation

Eden, Survivor0, Survivor1 으로 구성된다.

  • Survivor영역의 규칙 : Survivor0에 객체가 존재하면, Survivor1은 무조건 비워줘야 한다. 반대로 Survivor1에 객체가 존재하면 Survivor0은 무조건 비워줘야 한다.

GC의 과정

  1. 새로운 객체들이 Eden영역에 할당된다.
  2. 더 이상 할당될 공간이 없다.
  3. 이때 Minor GC가 발생한다.
    • Mark & Sweep과정이 발생한다.
    • reachable한 객체와 unreachable한 객체를 우선 마킹한다.
    • 여기서 살아남은 객체들이 Survivor0 영역으로 이동한다.
    • unreachable로 마킹된 객체들이 Sweep된다.
    • Survivor0 영역으로 이동해 있던 객체들의 age값이 증가한다.
    • 그리고 다시 Eden영역에 새로운 객체들이 할당된다.
    • 또 다시 Minor GC가 발생한다.
    • 이번에 살아남은 객체들은 Eden, Survivor0에서 Survivor1 영역으로 이동한다.
      - 이렇게 Minor GC가 발생할 때마다 객체들은 Survivor영역을 왔다 갔다 한다.
    • unreachable한 객체들이 지워지고, 살아남은 객체들의 age값이 증가한다.
  4. 객체의 age값이 임계점(age threshold)에 도달하면 해당 객체들은 Old Generation으로 이동한다.
    이 과정을 "promotion"라고 한다.
  5. Old Generation이 가득 차게 되면 Major GC가 발생한다.

GC가 이렇게 설계된 이유

설계자들이 다음과 같은 이유를 기반으로 GC를 설계하였다.

Weak Generational Hypothesis

  • 대부분의 객체는 금방 unreachable한 상태가 된다. 즉, 금방 garbage가 된다.
  • 오래된 객체에서 젋은 객체로의 참조는 아주 적게 존재한다.


실제로 Minor GC가 한 번 일어나면 상당 수의 객체들이 수거된다. 오히려 Minor GC가 자주 일어나는 것이 메모리 낭비를 막을 수 있다.
그렇기 때문에 young Generation을 Eden과 두 개의 Survivor영역으로 나눠 놓은 것이다.
반면에 Major GC는 한 번 일어나도 수거되는 객체들이 별로 없다.

Stop-the-World

GC를 실행하면 GC를 실행하는 스레드외의 모든 작업을 중단하는 현상이다.
GC종류에 따라서 Stop the World가 발생하는 시간이 다르다.

Stop-the-World시간을 줄이는 것이 어려운 최적화 작업이었다.

GC의 종류

  • Serial GC
  • Parallel GC
  • Parallel Old GC
  • CMS GC(Concurrent Mark Sweep)
  • G1 GC(Garbage First)

1. Serial GC

  • 가장 단순하고 기본적인 GC이다.
  • GC를 처리하는 스레드가 1개(싱글 스레드)이다.
  • 싱글 스레드 환경 및 Heap영역이 매우 작을 때 사용하기 위해 나온 방식이다.
  • 다른 GC에 비해 Stop-the-World 시간이 길다.
  • Mark-Compact(Sweep 포함) 알고리즘을 사용한다.

2. Parallel GC

  • Java8의 default GC
  • Young영역에 한해서 GC를 멀티 스레드로 수행
  • 멀티코어 환경에서 어플리케이션 처리 속도를 향상시키기 위해 사용된다.
  • Serial GC에 비해 Stop-the-World 시간 감소

3. Parallel Old GC

  • Parallel GC를 개선
  • Young영역만이 아니라 Old영역에서도 멀티 스레드 방식의 GC수행
  • Mark-Summary-Compact 알고리즘 사용
    - sweep : 단일 스레드가 old영역 전체를 훑는다.
    - summary : 멀티 스레드가 old영역을 분리해서 훑는다.

4. CMS GC(Concurrent Mark Sweep)

  • Stop-the-World 시간을 줄이기 위해 고안되었다.
  • 대부분의 가비지 수집 작업을 어플리케이션 스레드와 동시에 수행해서 Stop-the-World시간을 최소화시킨다.
  • 메모리와 CPU를 많이 사용하는 단점이 있다.
  • Compact과정이 없다. 그래서 메모리단편화 문제가 생길 수 있다.
  • reachable한 객체들을 한 번에 찾지 않고 순차적으로 찾는 것이 특징이다.
  • GC를 처리하는 스레드는 하나지만, 나머지 스레드들은 작업을 계속 진행한다.
    - Initial Mark : GC 루트에서 참조하는 객체들만 우선 식별
    - Concurrent Mark : 이전 단계에서 식별한 객체들이 참조하는 모든 객체 추적
    - Remark : 이전 단게에서 식별한 객체를 다시 추적. 추가되거나 참조가 끊긴 객체 확정
    - Concurrent Sweep : 최종적으로 unreachable한 객체들을 삭제한다.

5.G1 GC(Garbage First)

  • CMS GC의 메모리단편화 문제를 개선하여 CMS GC를 대체하였다.
  • Java 9+의 default GC
  • Heap영역을 young, old generation으로 물리적으로 나누지 않고 일정한 크기의 단위인 Region으로 나누어 어떤 영역은 young, 또 어떤 곳은 old로 활용한다.
  • G1 GC가 필요에 따라 영역별 Region개수를 튜닝한다.
  • 전체 Heap이 아닌 Region 단위로 탐색
  • Garbage만 있는 Region을 처음에 수거해가기 때문에 Garbage First라는 이름이 붙었다.
  • Mark를 할 때 전체 Heap영역을 훑지 않고 할당된 Region에 대해서만 찾는 방식으로 동작한다.
  • Compact 진행

JVM GC 튜닝

성능 개선의 최종단계이다.
객체 생성 자체를 줄이려는 코드 레벨에서의 노력이 선행되어야 한다.

GC 튜닝의 목표

  • Old Generation으로 넘어가는 객체를 최소화하기(Major GC 적게 발생시키기)
  • Major GC 시간을 짧게 유지하기

Major GC가 Minor GC보다 Stop-the-World시간이 길기 때문에 위의 목표가 설정되는 것이다.

한정된 Heap영역에 Young Generation과 Old Generation을 각각 얼마만큼 할당하는 것이 적당한지를 판단해야 한다.

  • 메모리가 너무 크다면 GC는 가끔 일어나지만 오래 걸린다.
  • 메모리가 너무 작다면 GC는 자주 일어나지만 금방 끝난다.

위의 두 사항을 고려하며 어플리케이션의 구조 및 특성에 따라 정도를 판단해야 한다.

GC 튜닝 과정

  1. 현재 GC 상태를 모니터링한다.
  2. 어플리케이션 특성에 알맞은 GC 방식과 메모리 크기를 설정한다.
  3. 옳다고 판단되면 적용한다.

GC 모니터링

  • JDK 설치 시 기본으로 제공되는 jstat이라는 툴을 통해 jvm을 모니터링 할 수 있다.
jstat -gcutil -t (프로세스번호) (몇초에 얼마)

위의 명령어를 통해 관련된 정보를 출력하게 할 수 있다.

시나리오 설정

  • 모니터링한 정보들을 바탕으로 여러가지 시나리오를 수립하고 테스트 해본다.

적용

  • 가장 적절하다고 판단되는 시나리오를 적용한다.
profile
개발공부하는사람
post-custom-banner

0개의 댓글