JVM GC(Garbage Collection)

해당 글은 JVM에 대한 이해를 하고 읽으면 좋습니다. 이미 해당 블로그에는 JVM에 대한 설명을 해둔 글이 있으니 먼저 읽어보시고 해당 글을 읽어보는것을 추천합니다.

 

JVM(Java Virtual Machine)

JVM이란 자바 JVM(Java Virtual Machine)은 자바 언어로 작성된 코드를 실행하기 위한 가상 머신을 말한다. JVM은 자바 언어의 특징인 플랫폼 독립성을 가능하게 하고, 자바 코드를 실행하기 위해서 중요

jamesblog95.tistory.com


GC(Garbage Collection)

JVM의 GC는 자바 가상 머신(Java Virtual Machine)에서 관리되는 메모리 영역 중 하나인 Heap 영역에서 객체들을 관리하는 작업을 말합니다. GC(Garbage Collection)는 Heap 영역에서 더 이상 사용되지 않는 객체들을 자동으로 제거해주는 기능입니다.

GC는 Heap 영역의 메모리를 효율적으로 관리하기 위해 필요합니다. Heap 영역에는 새로운 객체가 생성되고 사용되는 동안에도 메모리가 계속해서 할당됩니다. 이러한 메모리 할당이 지속되면 Heap 영역이 가득 차게 되고, 이후에 객체를 생성하려고 하면 OutOfMemoryError와 같은 예외가 발생합니다.

따라서 GC는 Heap 영역에서 사용되지 않는 객체를 자동으로 제거하여 더 이상 사용되지 않는 메모리를 해제하고, 새로운 객체가 Heap 영역에 할당될 수 있도록 공간을 유지합니다. 이러한 GC 작업은 일반적으로 백그라운드에서 수행되며, 개발자가 별도로 메모리 관리를 수행하지 않아도 자동으로 처리됩니다.

 

# 용어 설명

Heap 영역

http://asfirstalways.tistory.com/159

힙에서 영역은 크게 New/Young Generation, Tenured Generation 그리고 Permanent Generation 세 공간으로 구분이 됩니다. 

 

Stop-the-World

Stop-the-World는 JVM(Garbage Collector)이 Heap 메모리를 정리할 때 발생하는 일시적인 작업 중단 현상을 말합니다. 즉, GC 작업이 진행될 때 애플리케이션의 실행이 일시 중단되는 현상입니다.

 

reachability 와 unreachability

Reachability(도달 가능성)은 객체가 프로그램의 실행 흐름 상에서 접근 가능한 상태를 말합니다. 즉, 어떤 객체가 다른 객체를 참조하고 있는 상태를 말합니다. 객체가 도달 가능한 상태라면, 메모리에서 제거되지 않으며, 해당 객체를 참조하는 다른 객체에 의해 사용될 가능성이 있습니다.

Unreachability(도달 불가능성)은 객체가 프로그램의 실행 흐름 상에서 접근 불가능한 상태를 말합니다. 즉, 객체를 가리키는 참조가 존재하지 않는 상태를 말합니다. 객체가 도달 불가능한 상태라면, GC의 수거 대상이 되어 메모리에서 제거될 수 있습니다.

 

Root set

root set은 가비지 컬렉션에서 도달 가능한 객체를 판단하는 기준이 되는 객체 집합입니다. 이는 일종의 출발점이나 시작점으로서, 이루어진 객체를 기반으로 다른 객체들의 도달 가능성을 판단합니다.

 

힙에 있는 객체들에 대한 참조는 다음 4가지 종류 중 하나 입니다.

  • 힙 내의 다른 객체에 의한 참조
  • Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
  • 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
  • 메서드 영역의 정적 변수에 의한 참조

위의 4가지에서 ' 힙 내의 다른 객체에 의한 참조 ' 는 Unreachability 객체이며, 나머지는 root set으로 Reachability이 됩니다. 

http://asfirstalways.tistory.com/159

 

# New/Young Generation GC (MinorGC)

Young 영역은 3개의 영역으로 구분됩니다. 

 

  • Eden 영역
  • Survivor0
  • Survivor1

 

Yong 영역에 존재하는 객체를 관리하는 GC는 MinorGC라고 하며, Young영역에서 Old 영역까지 객체가 넘어 가는 과정은 아래와 같습니다. 

 

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

https://d2.naver.com/helloworld/1329

위의 절차를 따르게 되면 Survivor 두 개의 공간중 하나는 반드시 비어 있는 상태로 남아 있어야 합니다. 만약에 두 Survivor 모두 가득 차 있거나 데이터가 존재 한다면, 또는 두 영역 모두 사용량이 0 이라면 해당 시스템은 정상적인 상황이 아닌 것으로 판단 됩니다. 

 

# Tenured Generation GC (MajorGC or FullGC)

Old 영역은 new/Young Generation 영역에서 오래 살아남은 객체들이 존재하는 공간입니다. Old영역은 기본적으로 데이터가 가득 차면 GC를 수행하게 되는데, Old영역에서의 GC MajorGC(Full GC)라고 부릅니다. 그리고 MajorGC에서 사용되는 알고리즘은 mark-sweep-Compact 알고리즘 입니다. Old 영역에서는 GC처리 방식은 여러가지가 존재하고 각 GC방식은 아래와 같습니다.

 

Mark-sweep-compact 알고리즘
Mark(표시) 먼저 GC 루트(Heap 내에서 살아 있는 모든 객체에 대한 참조를 갖고 있는 객체)를 통해 시작해서, 접근 가능한 모든 객체를 표시합니다. 이 단계에서 GC는 Heap 내의 모든 객체를 확인하며, 이 중 접근 가능한 객체에 대한 마킹 작업을 수행합니다.

Sweep(정리) 다음으로, 표시되지 않은 객체(접근 불가능한 객체)를 모두 삭제합니다. 이 과정에서는 Heap 내에서 살아있는 객체와 삭제할 객체를 구분하여 메모리를 회수합니다. 이렇게 하면 Heap 내의 불필요한 객체들을 정리하여 남은 공간을 확보합니다.

Compact(압축) 마지막으로, 남은 객체들을 모아서 한쪽으로 밀고, 메모리 공간을 정리합니다. 이를 통해 Heap 내에서 각 객체들의 위치를 조정하며, 객체들이 차지하는 메모리 공간을 최소화합니다. 이 과정에서는 객체들이 차지하는 메모리 주소값을 변경하므로, 객체 간의 참조가 변경될 수 있습니다. 따라서, 참조 관련 작업이 필요합니다.

 

SerialGC

Serial GC은 Java Heap을 하나의 쓰레드로만 처리하는 방식의 GC입니다. 즉, GC 동작 시 JVM이 일시 정지되며, 하나의 쓰레드가 Heap 영역을 순차적으로 탐색하면서 쓸모 없는 객체를 제거합니다. 이 방식은 간단하고 빠르지만, 대규모 시스템에서는 GC 수행 시간이 길어져 성능 저하가 발생할 수 있습니다. 그래서 SerialGC는 메모리가 작고 CPU 코어 개수가 적을 때 사용하기 적합합니다.

 

Parallel GC

Parallel GC은 Serial GC와 달리 여러 개의 쓰레드를 사용하여 Java Heap을 처리하는 방식입니다. GC 수행 시에는 여러 개의 쓰레드가 동시에 Heap 영역을 탐색하면서 쓸모 없는 객체를 제거합니다. 이 방식은 대규모 시스템에서 높은 처리량을 보장할 수 있지만, GC 동작 시 JVM이 일시 정지되므로 서비스 중인 시스템에서는 이슈가 발생할 수 있습니다.

출처: https://mirinae312.github.io/develop/2018/06/04/jvm_gc.html

Parallel Old GC

Parallel Old GC는 Parallel GC와 달리 Old Generation 영역에 대해서 병렬 처리를 지원하는 GC입니다. Parallel Old GC는 Old Generation 영역의 객체들을 병렬적으로 탐색하며, 쓸모 없는 객체를 제거하고 메모리를 재활용합니다. 이 방식은 대규모 메모리를 가진 시스템에서 빠른 GC 처리를 보장할 수 있습니다. Parallel Old GC에서는 Mark-Summary-Compaction 알고리즘을 통해 객체를 제거하고 있습니다. 

 

Mark-Summary-Compaction 알고리즘
Mark(표시) Mark-Summary-Compaction 알고리즘은 Mark-Sweep-Compact 알고리즘과 마찬가지로 먼저 GC 루트(Heap 내에서 살아 있는 모든 객체에 대한 참조를 갖고 있는 객체)를 통해 시작해서, 접근 가능한 모든 객체를 표시합니다. 이 단계에서 GC는 Heap 내의 모든 객체를 확인하며, 이 중 접근 가능한 객체에 대한 마킹 작업을 수행합니다.

Summary(요약) 다음으로, 요약(Summary) 단계를 수행합니다. 이 단계에서는 Mark 단계에서 마킹된 모든 객체를 대상으로, GC 헤더(Header)에 있는 Summary 정보를 업데이트합니다. Summary 정보에는 객체의 크기, 클래스 정보 등이 포함됩니다. 이 단계에서는 Sweep 단계에서 수행하는 작업 중 일부를 대체하는 역할을 합니다.

Compact(압축) 마지막으로, 남은 객체들을 모아서 한쪽으로 밀고, 메모리 공간을 정리합니다. 이를 통해 Heap 내에서 각 객체들의 위치를 조정하며, 객체들이 차지하는 메모리 공간을 최소화합니다. 이 과정에서는 객체들이 차지하는 메모리 주소값을 변경하므로, 객체 간의 참조가 변경될 수 있습니다. 따라서, 참조 관련 작업이 필요합니다.

 

Concurrent Mark & Sweep GC (CMS)

Concurrent Mark & Sweep GC(CMS)는 Java Virtual Machine(JVM)에서 사용되는 GC 알고리즘 중 하나로, JVM이 동작 중일 때 GC를 수행하는 Concurrent GC입니다. CMS는 메모리 공간을 가장 효율적으로 사용할 수 있는 방법 중 하나로, 빠른 응답성과 최소한의 일시 중단을 보장합니다.

 

CMS는 크게 4개의 단계로 구성됩니다.

http://java.sun.com/j2se/reference/whitepapers/memorymanagement_whitepaper.pdf

  • Initial Mark: 해당 단계에서는 Root 객체들을 마킹합니다. 마킹이란 GC가 해당 객체를 추적하며, 메모리에서 삭제되지 않도록 하는 것을 의미합니다. 이 단계에서는 GC Root 객체들만 마킹하며, 이들을 마킹함으로써 GC가 추적할 수 있는 객체들의 범위를 한정합니다. 이 단계는 GC가 다른 객체를 마킹하기 전에 가장 빠른 시간 안에 수행됩니다.

  • Concurrent Mark: Initial Mark 단계 이후, GC는 실행 중인 애플리케이션과 함께 병행하여 객체를 마킹합니다. 애플리케이션이 수행되는 동안, GC는 마킹을 수행하는 쓰레드를 생성하여 병행적으로 마킹 작업을 수행합니다. 이 단계에서는 GC가 마킹하지 않은 객체도 계속해서 생성됩니다.

  • Remark: 애플리케이션이 실행되는 도중에는 마킹되지 않은 객체들이 생성됩니다. 따라서, 마지막 마킹 이후에도 GC는 추적 대상에서 벗어나는 객체가 발생할 수 있습니다. 이 때문에 GC는 Concurrent Mark 단계 이후, 다시 한번 객체를 마킹하는 작업을 수행합니다. 이 단계에서는 Initial Mark 이후 생성된 객체들 중 마킹되지 않은 객체들을 대상으로 마킹을 수행합니다.

  • Concurrent Sweep: 마지막으로 GC는 Sweep 작업을 수행하여, 마킹되지 않은 객체를 삭제하고 메모리 공간을 재사용합니다. 이 단계에서도 GC는 애플리케이션이 실행 중인 동안 병행 작업을 수행합니다. Sweep 작업을 수행하는 동안, GC는 마킹되지 않은 객체를 추적하여 삭제할 대상으로 선택합니다. 이후, 선택된 객체들을 Sweep하여 메모리 공간을 재사용 가능한 상태로 만듭니다.

 

G1(Garbage First) GC

G1(Garbage First) GC에서 "Garbage First"는 해당 알고리즘의 가장 중요한 특징 중 하나를 나타냅니다. G1 GC는 기존의 GC 알고리즘들과는 다르게 Garbage를 최우선적으로 수집하는 알고리즘이기 때문에 "Garbage First"라는 이름이 붙었습니다.

 

G1 GC는 Concurrent Mark & Sweep GC(CMS)와 마찬가지로 JVM이 동작 중일 때 GC를 수행하는 Concurrent GC입니다.

 

G1 GC에서는 전체 힙 공간을 블록 단위로 분할하고, 각 블록을 Young 영역 또는 Old 영역으로 구분합니다. 이때, G1 GC에서는 객체가 생성된 시점이나 객체의 크기와는 무관하게, 해당 객체가 속한 블록의 상태에 따라 Young 영역 또는 Old 영역으로 할당됩니다.

그림 6 G1 GC의 레이아웃(이미지 출처: "The Garbage-First Garbage Collector" (TS-5419), JavaOne 2008, p. 19)

G1 GC는 크게 4단계로 구성됩니다.

  • Initial Mark: 해당 단계에서는 Root 객체들을 마킹합니다. 이 단계는 CMS와 동일합니다.

  • Concurrent Marking: 애플리케이션이 실행 중일 때, GC는 객체를 마킹합니다. 마킹된 객체들은 Young 영역과 Old 영역으로 분류됩니다. 이 단계에서는 마킹되지 않은 객체들도 생성될 수 있습니다.

  • Remark: Concurrent Marking 이후, GC는 다시 한번 객체를 마킹하는 작업을 수행합니다. 이 단계에서는 Concurrent Marking 이후에 생성된 객체 중 마킹되지 않은 객체들을 대상으로 마킹을 수행합니다.

  • Cleanup: Cleanup 단계에서는 Young 영역과 Old 영역을 모두 스캔하여 마킹되지 않은 객체를 찾습니다. 이후, GC는 해당 객체들을 우선적으로 수집하여 Old 영역에서 가장 많이 발생하는 Garbage를 먼저 수집합니다. 이는 G1 GC에서 가장 중요한 특징 중 하나인 Garbage First라는 개념과 관련이 있습니다.

'Language > Java' 카테고리의 다른 글

Collections Framework  (0) 2023.04.26
객체 지향 프로그래밍(OOP)  (0) 2023.04.25
JVM(Java Virtual Machine)  (0) 2023.04.03
BigInteger, BigDecimal  (0) 2023.03.29
StringTokenizer, StringBuilder, StringBuffer  (0) 2023.03.27