[Java] Garbage Collection
Java

[Java] Garbage Collection

Garbage Collection이란?

우선 Garbage(가비지)라는 것은 유효하지 않은 메모리를 나타낸다. Java에서는 유효하지 않은 메모리를 JVM의 가비지 컬렉터가 불필요한 메모리를 알아서 정리해준다. Java에서는 대신 명시적으로 불필요한 데이터임을 표현하기 위해 일반적으로 null을 선언한다.

즉, 가비지 컬렉션(이하 GC)이란 메모리 관리 기법 중 하나로, 동적 할당한 메모리 영역 중 유효하지 않은 영역을 해제하는 기능이다.

  • 동적으로 할당한 메모리 영역 : heap 영역
  • 유효하지 않은 영역: 어떤 변수도 가리키지 않을 때
  • Java에서도 System.gc()를 호출할 수 있지만, 해당 메서드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 미칠 수 있다.

GC가 왜 필요할까?

따라서 GC를 도입함으로서 다음과 같은 장단점을 얻을 수 있다.

  • 장점
    • 메모리 누수 방지
    • 해제된 메모리에 접근 멈추기
    • 해제한 메모리 또 해제 X
  • 단점
    • GC 작업은 순수 오버헤드
    • 개발자는 언제 GC가 메모리를 해제하는지 모른다.

실시간성이 강조되면 GC에게 메모리 관리를 맡기는 것이 효율적이지 못할 수 있다.

GC 알고리즘

Reference Counting

  • Root space: Heap 영역 참조를 들고 있는 공간
    Reference Counting은 힙 영역 객체들이 각각 레퍼런스 카운트라는 숫자를 가지고 있다고 생각하면 된다. 레퍼런스 카운트는 몇가지 방법으로 해당 객체에 접근할 수 있는가를 나타낸다.
    레퍼런스 카운트가 0이면 가비지 컬렉션의 대상이 된다.
  • 레퍼런스 카운트의 한계 - 순환참조 문제
    Root space에서 Heap Space의 참조를 모두 끊으면, 사용하지 않은 메모리 영역이 순환 참조로 인해 해제되지 못할 수 있다.

Mark and sweep

Java , JS는 마크앤스윕으로 동작한다.

위 이미지는 root 부터 그래프 순회를 통해 연결된 객체를 찾아내는 Mark 과정이다.

위 이미지는 연결이 끊어진 객체를 지우는 Sweep을 나타낸다.
Sweep 이후에 분산되어있던 메모리는 위 초록 네모들처럼 예쁘게 정렬된다. 이를 Compaction이라고 하는데, 메모리 파편화를 막는 것을 말한다.

  • 장점
    • Mark and Sweep 방식을 사용하면 루트로부터 연결이 끊긴 순환 참조되는 객체들도 지울 수 있다.
  • 단점
    • 의도적으로 GC를 실행시켜야한다.
    • 어플리케이션 실행과 GC 실행이 병행된다.

JVM의 GC - JVM 구조

바이트 코드를 읽고 메모리의 힙 영역에 저장하는 Class Loader과 네이티브 코드로 변환시키고 GC를 실행하는 실행 엔진으로 구성되어있다.

JVM Memory 구조

  1. 모든 스레드가 공유하는 영역 / Method Area, Heap
  2. 스레드마다 공유하게 생성됨 / 나머지 3개

Heap - 메서드의 레퍼런스를 저장한다.
stack - stack 프레임이라는 블럭으로 쌓으며 로컬 변수, 중간 연산 결과 등이 저장된다.
pc register - pc가 현재 실행할 스택 프레임의 주소

노란 박스로 나타낸 부분이 Root Space를 나타낸다.

앞서 Java는 Mark and Sweep 방식을 이용한다고 했다.
그렇다면, 다음과 같은 두 가지 특징을 고려해 GC를 실행시켜야한다.

  1. 의도적으로 GC를 실행해야한다.
  2. 애플리케이션과 GC 실행을 병행한다.

JVM의 GC는 언제 실행될까?

JVM의 Heap은 크게 두 영역, Young Generation, Old Generation으로 나뉜다.
Young Generation에서 발생하는 GC는 minor gc, Old Generation에서 발생하는 GC는 major gc라고 부른다.

Young Generation은 또다시 세 영역, Eden / Survival 0 / Survival 1 영역으로 구분되는데, 각각은 다음과 같다.

  • Eden: 새로이 생겨난 객체들이 할당되는 영역
  • Survival: minor gc로 부터 살아남은 객체들이 존재하는 영역
    • Survival 0 혹은 Survival 1 중 하나는 꼭 비어 있어야 한다는 특징을 갖는다.

eden에 객체가 꽉차면 minor gc가 동작한다.
minor gc가 발생한 뒤 root로부터 reachable로 판단되면, surivial 0으로 이동한다. (이렇게 eden이 꽉차고 minor gc가 반복되면 sur0, sur1 등으로 번갈아가며 이동한다.) 이 때, 살아남은 객체들의 age bit는 상승한다.

  • promotion: young generation surv에 오랫동안 있었던 것들은 old로 이동하는 것이다.
    • 즉, JVM GC에서는 일정 수준의 age-bit를 넘어가면 오래 참조될 객체라고 판단하고 Old Generation에 해당 객체를 넘겨준다.
    • 자바8기준 age bit가 15가 되면 프로모션이 실행된다.

Stop the world

Stop the world란 GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것을 말한다. 따라서 Stop The World 시간을 최소화할 필요가 있다.

시리얼 GC

  • 하나의 쓰레드로 GC를 실행시킴
  • stop the world가 길다.

병렬 GC

  • 여러개 쓰레드로 GC를 실행
  • 멀티코어 환경
  • java 8의 디폴트 gc 방식

CMS GC (Concurrent Mark Sweep)

  • stw최소화를 위해 고안
  • gc 작업을 애플리케이션과 동시에 실행
  • 메모리와 cpu를 많이 사용된다.
  • g1 gc의 등장에 따라 대체됨

G1 GC

  • Garbage first
  • 힙 영역을 region으로 나누어 사용
  • java9부터 디폴트