[Java] - GC(Garbage Collection)

chancehee·2022년 12월 14일
0

자바

목록 보기
10/12
post-thumbnail

0. 글을 작성하는 이유

Java는 다른 대부분의 언어와 다르게 메모리 관리를 직접해줄 필요가 없습니다.
매우 편리한 기능이지만, 익숙함에 속아 동작 과정을 모르면 안되겠죠?
Java에서는 Garbage Collection 기능을 통해서 메모리를 관리합니다.
그렇다면 GC는 어디에서 동작하고 어떤 방식으로 동작하는지 살펴보겠습니다.🧹🤔

1. JVM 구조

  • Garbage Collector는 Execution Engine에 존재합니다.

2. Execution Engine

Runtime Data Area에 할당된 바이트 코드를 실행시키는 주체입니다.
코드를 실행하는 방식은 크게 2가지 방식이 존재합니다.

[구성 요소]

  • Interpreter
    바이트 코드를 해석하여 실행하는 역할을 수행합니다.
    다만 같은 메소드라도 여러번 호출될 때 매번 새로 수행해야 합니다.
  • JIT(Just In Time) Compiler
    반복되는 코드를 발견하여 전체 바이트 코드를 컴파일하고 그것을 Native Code로 변경하여 사용합니다.
    이를 통해 Interpreter의 단점을 해소합니다.
  • Garbage Collector
    더 이상 참조되지 않는 메모리 객체를 모아 제거하는 역할을 수행합니다.
    일반적으로 자동으로 실행되지만, 수동으로 실행하기 위해 System.gc()를 사용할 수 있습니다. (단, 실행이 보장되지는 않고 직접 실행시 성능의 저하를 가져올 수 있기 때문에 직접 호출은 사용하지 않는 것이 좋습니다.)

3. Garbage Collecion

앞으로 사용되지 않는 객체의 메모리(Garbage)를 정해진 스케줄에 의해 정리해주는 것

[특징]

  • 자바의 메모리 관리 기법으로 어플리케이션이 동적으로 할당했던 메모리 영역 중 더이상 사용하지 않는 영역을 정리하는 기능입니다.
  • GC는 Heap 메모리에서 활동하며, JVM에서 GC의 스케줄링을 담당하여 개발자가 직접 관여하지 않아도 더이상 사용하지 않는 점유된 메모리를 제거해주는 역할을 담당합니다.

[Stop The World]

  • GC를 수행하기 위해 JVM이 멈추는 현상을 의미합니다.
  • GC가 작동하는 동안 GC관련 Thread를 제외한 모든 Thread는 멈춥니다.
  • 일반적으로 'GC 튜닝'이라는 것은 이 시간을 최소화하는 것을 의미합니다.

[GC의 종류]

  • Serial GC
  • Parallel GC
  • CMS GC
  • G1 GC
  • Z GC

[GC의 원리]

  • GC는 '약한 세대 가설(weak generational hypothesis)'을 사용하여 동작합니다.
  • 아래 가정을 기반으로 메모리 구조를 크게 2개의 물리적 공간으로 나눕니다. (Young Generation, Old Generation)
    1. 대부분의 객체는 금방 접근 불가능(Unreachable)한 상태가 된다.
    - 대부분의 객체는 중괄호 '{}' 안에서 생성
    - 이 객체들은 괄호가 끝나는 시점에서 더이상 사용되지 않음
    - 특수한 경우에는 오래 사용할 수 있지만, 대부분의 경우 unreachable한 상태가 되어 GC의 대상이 됨
    2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 발생한다.
    - 일반적으로 순차적인 로직에 의해 객체를 생성하여 활용
    - 이 과정에서 앞에 생성된 객체는 그 다음의 로직에서 사용된 이후 대부분 사용되지 않게 됨
    - 이런 특성으로 인해 더이상 참조되지 않는 객체에 대해 GC를 할 수 있게 됨

4. GC Algorithm

GC는 기본적으로 Garbage 대상을 식별하고 그 대상을 메모리에서 제거하는 것으로 기본 동작 관련 알고리즘은 여러 종류가 있습니다.

여러 종류의 알고리즘 중에서도 대표적인 알고리즘 몇개만 살펴보겠습니다 🤓

1. Reference Counting Algorithm

  • Garbage 탐색에 초점을 맞춘 알고리즘입니다.
  • 각 객체마다 Reference Count를 관리하며, 이 카운트가 0이 되면 GC를 수행합니다.
  • 카운트가 0이 되면 바로 메모리에서 제거된다는 장점이 있지만, 순환 참조 구조에서 Reference Count가 0이 되지 않는 문제가 발생하여 Memory Leak이 발생할 수 있는 단점이 있습니다.

2. Mark-and-Sweep Algorithm

  • Reference Counting Algorithm의 단점을 극복하기 위해 나온 알고리즘입니다.
  • 2가지 단계, Mark 단계 + Sweep 단계로 동작합니다.
  • RSet(Root Set)에서 Reference를 추적하여 참조 상황을 파악합니다.
  • Mark 단계에서 Garbage 대상이 아닌 객체를 마킹하는 작업을 수행합니다.
  • 그 이후 Sweep 단계에서 마킹되지 않은 객체를 지우는 작업을 수행합니다.
  • 위 단계를 마친 후 마킹 정보를 초기화합니다.
  • 이 방식은 GC가 동작하고 있을 경우, Mark 작업과 어플리케이션 Thread의 충돌을 방지하기 위해 Heap 사용이 제한됩니다.
  • Compaction 작업이 없어서 비어 있는 공간이 충분하지 않을 경우 Out Of Memory가 발생할 수 있는 단점이 있습니다.

3. Mark-and-Compact Algorithm

  • Mark-and-Sweep Algorithm에서 발생하는 점유 메모리 분산(Fragmentation)을 해결하기 위해 나온 알고리즘입니다.
  • 3가지 단계, Mark 단계 + Sweep 단계 + Compact 단계로 동작합니다.
  • Compact 단계에서 윈도우 운영체제의 '디스크 조각 모음'기능 처럼 흩어져 있는 메모리를 모아주는 작업을 진행합니다.
  • Compact 작업으로 메모리 효율을 높일 수 있는 장점이 있지만, Compact 작업과 그 이후 Reference를 업데이트하는 작업으로 인해 오버헤드(Overhead)가 발생할 수 있는 단점이 있습니다.

5. 일반적인 GC 과정 (Generational Algorithm)

GC는 Heap 메모리의 각 영역을 활용하여 최적의 메모리 운영을 합니다.

[동작 과정]

  1. 맨 처음 객체가 생성되면 Eden 영역에 생성됩니다.
  2. 그리고 GC의 일종인 Minor GC가 발생하면 미사용 객체의 제거와 함께 아직 사용되고 있는 객체는 Survivor1, Survivor2 영역으로 이동 시킵니다.
    (단, 객체의 크기가 Survivor 영역의 크기보다 클 경우에는 바로 Old Generation으로 이동합니다.)
  3. 운영 특성상, Survivor1과 Survivor2 영역은 둘 중 한 곳에만 객체가 존재하게끔 운영되며, 다른 한 곳은 비어져 있어야 합니다.
    보통 From, To로 영역을 구분하며, 객체가 존재하는 Survivor(From) 영역이 가득 차면 다른 Survivor(To) 영역으로 보내고 기존의 Survivor(From) 영역을 비우는 작업을 진행합니다.
  4. 위 과정 (1~3번)을 반복하면서 Survivor 영역에서 계속 살아남은 객체들에게 일정 Score(From -> To 이동시에 증가)가 누적이 되어 기준치 이상이 되면 Old Generation 영역으로 이동하게 됩니다.(Promotion 발생!)
  5. Olde Generation 영역에서 살아남았던 객체들이 일정 수준 쌓이게 되면, 미사용으로 판단된 객체들을 제거해주는 Full GC가 발생하고, 이 과정에서 Stop The World가 발생합니다.

6. GC의 종류

Serial GC

하나의 CPU로 Young Generation과 Old Generation을 연속적으로 처리하는 방식입니다. 가장 오래된 GC이며 Mark-and-Compact 알고리즘을 사용합니다. GC가 수행될 때 STW가 발생합니다.

Parallel GC

Java 7~8 버전에서 Default로 설정되어 있는 GC로, Parallel GC의 목표는 다른 CPU가 GC의 진행시간 동안 대기 상태로 남아 있는 것을 최소화 하는 것입니다. GC 작업을 병렬로 처리하여 STW 시간이 비교적 짧습니다.

Parallel Compacting GC

Parallel GC에서 Old Generation의 처리 알고리즘을 변경한 것입니다.

Concurrent Mark-Sweep(CMS) GC

Application의 Thread와 GC Thread가 동시에 실행되어 STW를 최소화 하는 GC입니다.
Paraller GC와 가장 큰 차이점은 Compaction 작업 유무입니다.

Garbage First(G1) GC

큰 메모리에서 사용하기 적합한 GC 입니다.
(대규모 Heap 사이즈에서 짧은 GC 시간을 보장하는데 목적을 두고 있습니다.)
전체 Heap 영역을 Region이라는 영역으로 분할하여 상황에 따라 역할이 동적으로 부여됩니다.
G1GC는 Java 9 이후 Default GC 이며, 고용량의 메모리를 사용하는 어플리케이션에 최적화된 GC 입니다.

Z GC

ZPage라는 영역을 사용하며, G1GC의 Region은 크기가 고정인데 비해 Zpage는 2mb 배수로 동적으로 운영됩니다.
정지 시간이 최대 10ms를 초과하지 않는 것을 목적으로 운영이 되고, Heap 크기가 증가하더라도 정지 시간이 증가하지 않는 것이 특징입니다.

7. 결론

요즘은 하드디스크의 발전이 비약적으로 증가했고 과거에 비하면 저렴한 가격에 큰 용량의 메모리를 구입할 수 있습니다.
하지만, 메모리는 결국에는 유한한 영역입니다. 따라서 한정된 영역을 잘 사용하기 위한 메모리 관리가 필수적입니다.
Java에서는 Garbage Collection 이라는 기능을 통해서 메모리 관리를 제공해줍니다. 또한 GC는 JDK 버전별로 Default GC가 달라져왔으며 시간이 지난 미래에는 또 다른 GC가 등장할 수도 있습니다.
GC는 결국 STW를 감소시키는 방법을 강구하는 것이고, 사용되는 어플리케이션 규모에 따라서 적합한 GC를 사용하기 위해서는 (또는 왜 Default GC인지 알고 쓰는 것) GC에 대해서 명확하게 아는 것이 중요하다고 생각합니다.💡

출처: https://www.youtube.com/watch?v=jXF4qbZQnBc

0개의 댓글