자바 Garbage Collection & Garbage Collector

HYK·2022년 10월 7일
1

Java

목록 보기
4/4

❗️❗️ 잘못된 정보는 알려주시면 감사합니다!

부끄럽게도 자바를 공부하면서 Garbage Collection/Garbage Collector 둘의 차이를 몰랐었다.. 그다지 중요하지 않다고 생각해서 그냥 넘어갔던 것 같다.
GC를 공부하다 보면 왜 GC에 대해서 잘 알고 있어야 하는지 알게 된다.
GC를 자주 하게 되거나 GC 영역이 클 경우에는 GC로 인해서 여러 가지 이슈가 발생할 수 있다. 뒤에 따로 설명하겠지만 GC를 실행할 동안에는 모든 스레드가 작업을 멈추고 GC 작업을 수행하는 데 이때 내 프로그램은 멈춰있게 된다. 만약 내가 운영하는 웹서버에 GC가 일어날 때마다 서버가 멈추게 되고 이를테면 트래픽이 몰리는 시간에 이런 일이 일어났다가는 과부하로 인해 장애가 날 수도 있다. 이외에도 여러 가지 성능상 문제가 존재하게 되는데 GC의 수집 조건을 정확하게 알지 못하면 GC가 제거하지 못하는 객체로 인해 OOM이 발생할 수도 있다. 이와 같은 실수를 하지 않기 위해서는 GC에 대해서 잘 알고 있어야 한다.


Garbage Collection? Garbage Collector?

보통 GC는 Garbage Collection을 말한다.

Garbage Collector은 뭐고 Garbage Collection는 뭘까?

  • Garbage Collection : 사용하지않는 메모리 공간의 메모리를 해제하는 행위를 말한다. 즉 쓰레기를 청소하는 행위를 Garbage Collection 이라고 한다.

    가비지컬렉터가 가비지 컬렉션을 수행한다.

  • Garbage Collector : Garbage Collection을 수행하는 주체로 root space로 부터 참조되고 있지않는 객체들을 가비지로 판단하여 이객체들의 메모리 공간을 회수하는 역할을 하는 프로그램을 말한다.

GC의 장단점과 필요한 이유

GC가 왜 필요할까??

메모리라는 것은 한정된 자원이다 누군가는 이 메모리를 관리해야 된다. 처음에는 이 메모리 관리를 개발자가 직접 했다.
대학교 C언어 시간에 한 번쯤을 봤을 법한 malloc/free 함수들이 바로 이 메모리를 관리하는 함수들이다 이 함수를 이용해서 개발자가 직접 메모리를 할당 해제하면서 관리를 했다. 생각해 보자 간단한 프로젝트 하나를 하는데도 객체들을 몇 개나 만들어내며 일일이 그것들을 하나씩 할당/해제를 한다면 이 얼마나 귀찮고 불편한 작업일까??
그렇다고 해서 메모리를 할당받고 해제를 하지 않는다면 아주 큰 문제가 생긴다. 바로 메모리 누수(memory leak)라는 문제다. 이 메모리 누수가 발생해서 누적된다면 결국 프로그램은 OutOfMemory를 뿜으면서 종료될 것이다.

이쯤 되면 GC가 필요한 이유를 알 수 있다.
이 귀찮은 작업들을 바로 GC가 대신 처리해 주는 것이다. GC의 장단점을 정리해 보자
(물론 GC를 사용하더라도 개발자의 실수로 인해서 메모리 누수는 여전히 발생할 수 있다.)

  • 장점
    1. 앞서 얘기했듯이 프로그래머가 직접 메모리를 관리할 필요가 없어진다. 따라서 실수로 메모리 해제하지 않는 경우를 줄일 수 있기 때문에 실수로 인한 메모리 누수를 줄일 수 있다.
    2. 해제된 메모리에 접근하건 나 해제된 메모리를 다시 해제하는 그러한 오류들을 방지할 수 있다.
  • 단점
    1. GC라는 작업을 JVM이 처리해야 되기 때문에 직접 작성할 때보다 Overhead가 발생하게 된다
    2. GC의 타이밍을 정확히 예측할 수 없다. 프로그래머가 메모리 공간이 언제 해제되는지 정확히 예측 알 수 없는 단점이 있다.

직접 GC과정을 살펴보자

테스트 환경 :jdk version : jdk-8 , option :-ea -verbose:gc -XX:+UseSerialGC

클릭시 영상 재생

테스트 환경 :jdk version : jdk-8 , option :-ea -verbose:gc -XX:+UseParallelGC

클릭시 영상 재생

생각해보기

GC과정

  1. 새로운 객체들은 Eden 영역에 생성된다.
  2. Eden 영역이 꽉 차게 되면 잠깐의 Stoptheworld 상태가 되고 minor gc (young gc)가 일어난다. (이때 Mark and Sweep 방식으로 일어나게 됨)
  3. 이때 gc 이후 살아남은 객체들은 survivor 영역으로 복사된다.
  4. survivor 영역이 꽉 찬다면 2번의 gc 과정이 반복되고 살아남은 객체들은 age가 증가된 상태로 비어있는 다른 survivor 영역으로 복사된다.
  5. survivor 영역에 있던 객체들은 특정 age 이상이 되면 old 영역으로 이동하게 되는데 이 과정을 Promotion이라고 한다.
  6. 5번의 과정이 반복되어 old 영역도 꽉 차게 된다면 이때 major gc(full gc)가 발생하게 되는데 마찬가지로 Stoptheworld 상태가 되고 Mark and Sweep 방식에 compact를 추가해서 실행된다. (메모리 단편화 제거)

Stop-the-world?(STW)

번역하면 세상을 멈추다 이런 뜻이 된다. 자바에서는 GC를 수행하기 위해서 GC 수행 외에 모든 스레드 작업을 멈추고 GC를 수행한 후 다시 이어서 작업을 수행한다. 이때 모든 작업을 멈추는 것을 Stop-the-world 라고 하는데
GC 과정중에 객체를 재배치하는 과정있는데 이때 객체 그래프가 일치하지 않기 때문에 Stop-the-world가 필요하다고 한다.
또 Stop-the-world가 길어지면 장애가 발생할 수 있겠다고 유추할 수 있다. 대개 GC 튜닝은 GC Time을 줄이는 게 목적이다

Mark and Sweep(and compact)

GC 알고리즘 중 Reference Counting라는 알고리즘이 있는데 이는 순환 참조라는 문제가 있었기 때문에
보통 Mark and Sweep(compact) 알고리즘 방식을 이용한 GC가 사용된다.

Mark and Sweep and compact의 진행 순서

  1. Root로부터 참조되고 있는 객체들은 이렇게 Mark 해둔다.


2. 마크되지 않는 객체들은 삭제시킨다(Sweep)

3. 빈 공간들이 생기지 않도록(메모리 단편화) 객체를 재배치 시킨다.(compact)

로그에 나오는 Allocation Failure , Ergonomics 는 뭘까?

영상을 보면 Allocation Failure(할당 실패)은 영역이 꽉 찼을 때 공간을 확보하기 위해 GC가 일어나는 이유를 설명해 주고 있다.
Ergonomics는 Serial GC에서는 일어나지 않지만 Parallel부터는 Ergonomics라는 이유의 GC가 일어나는데 이는 JVM이 판단해서 현재 GC가 적합할 때라고 판단해서 GC 하는 경우를 나타낸다 그래서 old 영역이 꽉 차지 않았을 때도 GC 가 발생하는 것을 볼 수 있다.

왜 survivor 영역 왜있으며 2개일까?

heap의 young 영역에서는 GC 시에 Mark and Sweep 방식으로 GC가 일어나지만 compact 작업은 일어나지 않는다 따라서 메모리 단편 화가 발생한다 이때 GC 후에 영역을 옮기면서 재배치를 시키는데 이때 메모리 단편화를 제거해 준다.

  • eden 영역 단편화 -> survivor 영역으로 옮기면서 제거
  • survivor 영역 0의 단편화 -> survivor 영역 1로 옮기면서 제거 (1->0 0->1반복)

이런 식으로 계속 객체들을 재배치시키면서 단편화를 제거시킨다.

OutOfMemory

영상 속 old 영역을 보면 GC가 되더라도 계속 차는 걸 볼 수 있다. 의도적으로 OOM 되도록 코드를 짰기 때문이다.
"사용하지 않는 객체들은 GC가 알아서 제거해 준다면서요!" 맞다 근데 조건이 있다.
참조하지 않는 객체를 사용하지 않는 객체로 판단하는 것이지 참조는 되고 있는데 사용하지 않는 객체들은 사용할 수도 있다고 판단하기 때문에 GC 대상이 아니게 된다. 즉 개발자가 실수로 사용하지 않는 객체의 참조를 그대로 놔둔다면 그 객체는 계속 살아있게 된다 예를 들면 Map 내부에 캐시 된 객체들을 생각할 수 있다. 캐시를 사용하지 않을 때 그때그때 제거하거나 캐시 크기를 지정해놓지 않는다면 메모리 누수가 발생할 것이다.


왜 full GC는 young gc에 비해서 느릴까?

상대적으로 oung GC에 비해서 GC 영역이 크기 때문이다.
만약 old 영역을 작게한다면 GC의 속도는 빨라질 수 있지만 GC의 빈도가 자주 발생할 수 있다.


Garbage Collector 종류

1.Serial GC

  • 싱글스레드로 동작한다.
  • 싱글스레드로 동작하기 때문에 STW 시간이 길다.
  • 앞서 살펴본 Mark and Sweep and Compact 방식을 사용한다.

2.Parallel GC

  • java 8의 기본 GC이다.
  • 멀티스레드로 동작한다.(young 영역만)
  • 멀티스레드를 사용하기 때문에 Serial GC에 비해서 STW 시간이 짧다.

3.Parallel Old GC

  • 멀티스레드로 동작한다.(모든 영역)
  • 스레드의 개수를 지정할 수 있다.

4.CMS GC

  • STW 시간을 최소화하기 위해서 만들어졌다. (애플리케이션 작업과 동시에 GC 함)
  • 현재는 deprecated된 상태이다.
  • CMS GC 4단계를 알아보자
    • Initial Mark : Root가 참조하는 객체를 빠르게 marking 하는 단계 (STW가 발생하지만 짧다.)
    • Concurrent Mark : 애플리케이션 작업과 동시에 이전 단계에서 마킹된 객체가 참조하는 객체를 대상으로 살아있는 객체들을 추가로 Marking 한다. (STW가 일어나지 않지만 애플리케이션 작업과 동시에 일어나기 때문에 overhead가 발생한다.)
    • Remark : 이전 과정에서 추가된 가비지 객체가 있을 수 있기 때문에 추가적인 가비지 대상이 있는지 확인한다.(STW가 발생 멀티 스레드로 동작)
    • Concurrent Sweep : GC 대상을 메모리에서 제거한다.(마찬가지로 STW가 일어나지 않지만 overhead 발생)

5.G1 GC

  • java 9의 기본 GC이다.
  • 앞서본 GC 중에서 가장 빠르다.
  • 이전 heap 구조와는 다른 heap 구조를 가진다. heap을 각 Region 나누어 관리한다.
  • 전체 (old, young..) 영역을 탐색하는 게 아닌 나누어진 일정한 크기의 Region을 단위로 탐색하기 때문에 속도가 빠르다

Ref
https://stackoverflow.com/questions/10695298/java-gc-why-two-survivor-regions
https://stackoverflow.com/questions/16695874/why-does-the-jvm-full-gc-need-to-stop-the-world
https://plumbr.io/handbook/garbage-collection-algorithms-implementations/parallel-gc
https://www.youtube.com/watch?v=Fe3TVCEJhzo&t=233s

profile
Test로 학습 하기

0개의 댓글