[정리] Java Virtual Machine Garbage Collector(GC) 에 대해 알아보자 (3)

Wook _·2023년 9월 7일
0

정리

목록 보기
6/13

Garbage Collector!

이전 포스트에서 그랬듯 오늘도 JVM 관련 포스트이다.

그 중에서 메모리 영역을 관리해주는 GC가 오늘의 주제이다.

그럼 바로 알아보자.


1. GC가 뭐지?

Garbage Collector. 줄여서 GC다.
앞으로 편의상 GC라고 하겠다.

GC는 Heap 메모리 영역 중, 유효하지 않은 메모리인 Garbage를 자동으로 제거해주는 기능이다.

JVM의 기능 중 더 이상 사용하지 않는 객체를 청소하여 메모리 공간을 확보해주는 작업이다.
자바나 코틀린을 이용하게 되면 JVM의 GC가 불필요한 메모리를 알아서 정리해준다.

대신 자바에서 명시적으로 불필요한 데이터를 표현하기 위해 일반적으로 null을 선언해준다.

Object o1 = new Object();
o1.setNumber(1);
o1 = null;

//Garbage 발생
o1 = new Object();
o1.setNumber(2);

기존 생성된 객체1을 참조하지 않고 새로운 객체2를 참조하면서 객체1이 Garbage가 되었다.

GC는 이러한 Garbage를 정리해준다.

Stop The World
GC를 수행하기 위해 JVM이 애플리케이션의 실행을 일시 정지하는 것이다.

GC가 실행되면 GC 작업을 맡은 쓰레드를 제외한 나머니 쓰레드는 모두 일시 정지되며, GC 작업이 종료되면 재개한다.


2. GC는 항상 좋은것인가?

GC가 장점만 있는것은 아니다.

각 장단점을 정리해보자.

장점

  • 개발자의 실수로 인한 메모리 누수 방지
  • 이미 해제된 메모리를 다시 해제하는 이중 해제 방지
  • 해제된 메모리 접근 방지

단점

  • 개발자가 메모리를 언제 해제되는지 정확하게 알 수 없다.
  • GC가 동작하는 동안은 다른 동작이 멈추기 때문에 오버헤드가 발생한다.

3. GC가 일어나는 Heap Memory 구조를 봐보자.

Heap Memory는 두 가지 영역으로 나뉜다.

  1. Young Generation 영역
  2. Old Generation 영역

우선 Young Generation 영역부터 보자.

Young Generation
짧게 살아남는 메모리들이 존재하는 공간이다.

모든 객체는 처음에 Young Generation에 생성되며, Young Generation은 Old Generation에 비해 상대적으로 적기 때문에 메모리 상의 객체를 찾아 제거하는데 적은 시간이 걸린다.

작은 공간에서 데이터를 찾기 위해 걸리는 시간이 적기 때문이다.

Young Generation에서 발생되는 GC는 Minor GC라고 불린다.

Old Generation

길게 살아남는 메모리들이 존재하는 공간이다.

Old Generation은 Young Generation에서 시작되었으나, GC 과정 중에서 제거되지 않은 경우 Old Generation로 이동한다.

Old Generation은 Young Generation에 비해 상대적으로 큰 공간을 가지고 있으며 이 공간에서 메모리 상의 객체 제거에 많은 시간이 걸린다.

이 때문에 Old Generation에서 발생되는 GC는 Major GC라고 불린다.

Young Generation은 세부적인 영역을 가지고 있다.

  • Eden
  • Survivor0
  • Survivor1

초기에는 Eden영역에서 객체가 생성되며 GC가 일어날때마다 살아남은 객체는 Survivor0을 거쳐 Survivor1로 이동한다.
Survivor0과 Survivor1 중 하나는 비워 있어야하는 규칙이 있다.

이때 minor GC는 Eden 영역이 꽉 찼을 때 발생한다.
GC가 없애는 객체는 랜덤이다. 때문에 개발자는 알 수 없다.

Survivor1에서도 살아남는다면 Old Generation로 이동한다.

그렇다면 왜 Young Generation과 Old Generation이 나뉘어진걸까?

그것은 GC 설계자들이 애플리케이션을 분석해보니 대부분의 객체의 수명이 짧다는 것을 인지했기 때문이다.

GC도 결국 비용이 드는 작업인 만큼, 대부분 객체들의 수명이 짧으니 메모리의 전체보단 특정 부분만 탐색하여 해제하는 것이 효율적이기 때문이다.


4. GC가 사용하는 알고리즘에 대해 알아보자.

GC는 대표적으로 두 가지 알고리즘을 사용한다.

  1. Reference Counting
  2. Mark and Sweep

먼저 Reference Counting에 대해 알아보자

Reference Counting

그림 속 Root Space는 스택 변수, 전역 변수 등 Heap 영역 참조를 가리키고 있는 공간이다.

Reference Counting은 Heap 영역의 객체들이 각각 Reference Count라는 숫자를 가지고 있다.

Reference Count는 몇가지 방법으로 해당 객체에 접근할 수 있는 지를 보여준다.

만약 Reference Count가 0이 되면 해당 객체에 접근할 수 있는 방법이 없다는 뜻이므로 GC의 대상이 된다.

하지만 Reference Counting은 순환 참조 문제가 발생할 수 있다.

그림처럼 서로가 서로를 참조하는 것이 순환 참조이다.
순환 참조가 발생하면 서로가 서로를 참조하기 때문에 Reference Count는 1로 유지되어 GC 대상임에도 메모리 해제가 불가능하여 메모리 누수를 발생시킨다.

이를 방지하기 위한 또 다른 알고리즘이 바로 "Mark and Sweep"이다.

Mark and Sweep

Mark and Sweep 알고리즘은 Reference Counting의 순환 참조를 해결할 수 있다.

Mark and Sweep은 Root Space로부터 해당 객체에 접근이 가능한지 여부를 메모리 해제의 기준으로 삼는다.

수행 순서는 다음과 같다.
1. Mark: Root Space부터 그래프 순회를 통해 연결된 객체를 찾아낸다.
2. Sweep: 연결이 끊어진 객체를 지운다.
3. Compaction: 분산된 메모리를 정리하여 메모리 파편화를 방지한다.

다만 Mark and Sweep에서 Compaction은 필수가 아니다.

Mark and Sweep 방식도 단점이 있는데, 객체의 Reference Count가 0이 되면 지워버리는 Reference Counting과 달리, Mark and Sweep은 의도적으로 특정 순간에 GC를 실행해야 한다.

즉 어느 순간에는 실행중인 애플리케이션이 GC에게 컴퓨터 리소스를 내어주어야한다.


5. GC의 실행 방식에 대해 알아보자.

Mark And Sweep 방식의 특징 중 하나는 애플리케이션과 GC 실행이 병행된다는 것이다.

즉 JVM에서 애플리케이션과 GC를 병행하여 실행할 수 있는 여러 옵션을 제공한다.

각 실행방식에 대해 알아보자.

  1. Serial GC
  2. Parallel GC
  3. Parallel Old GC

각 방식에 대해 알아보자.

Serial GC

하나의 쓰레드로 GC를 실행하는 방식이다.

하나의 쓰레드로 GC를 실행하다 보니 Stop The World가 길다.
싱글 쓰레드 환경 및 Heap 영역이 매우 작을 때 사용되는 방식이다.

Mark And Sweep 진행 후 Compaction 과정도 진행된다.

Parallel GC

기본적인 처리과정은 Serial GC와 동일하지만 Parallel GC은 여러 개의 쓰레드로 GC를 실행하므로 앞선 Serial GC보다 Stop The World가 짧다.

멀티 코어 환경에서 애플리케이션 처리 속도를 향상시키기 위해 사용되며, Java 8에서 기본으로 쓰이는 GC 방식이다.

Parallel GC은 GC의 오버헤드를 상당히 줄여주었지만, 애플리케이션이 멈추는 것은 여전했기에 다른 알고리즘이 등장하게 되었다.

일반적으로 Parallel GC은 minor GC에 대해서만 멀티 쓰레딩을 수행하도, major GC에는 싱글 쓰레딩을 수행한다.

CMS GC

Concurrent-Mark-Sweep의 줄임말로 Stop The World 시간을 최소화 하기 위해 고안되었다.

대부분의 가비지 수집 작업을 애플리케이션 쓰레드와 동시에 수행하여 Stop The World 시간을 최소화한다.

하지만 메모리와 CPU를 많이 사용하고, Mark And Sweep 진행 후 Compaction이 기본적으로 제공되는 않는 단점이 있다.

이 때문에 시스템이 장기적으로 운영되다가 조각난 메모리들이 많아 Compaction을 수동으로 수행하면 오히려 Stop The World의 시간이 길어짐을 알 수 있다.

CMS GC는 Java 9 버전부터 deprecated 되었고 Java 14버전부터 사용이 중지되었다.


이상으로 GC에 대해 알아보았다.

끝!


출처

https://steady-coding.tistory.com/584
https://devfunny.tistory.com/681
https://kotlinworld.com/340#GC%EA%B-%--%--%EC%-D%BC%EC%--%B-%EB%--%--%EB%-A%--%--%EB%B-%A-%EC%-B%-D%EA%B-%BC%--Heap%--Memory
https://coding-factory.tistory.com/829

profile
책상 위에 있는 춘식이 피규어가 귀엽다.

0개의 댓글