Programming Language/Java

JVM의 Garbage Collection 과정

LeeJerry 2021. 8. 20. 05:14

 

원래는 이 주제를 음주 글쓰기로 쓰고 있었는데, 다음날 제정신 차리고 보니까 도저히 아니다 싶어서 싹 갈아 엎고 다시 씀.

 


Stop - The - World

 

GC가 가비지 컬렉션을 수행할 시, 가비지 컬렉션을 실행하는 쓰레드를 제외하고 나머지 모든 쓰레드는 잠깐 멈춘다. 이 멈춰진 쓰레드들은 가비지 컬렉션이 실행되고 난 이후에 다시 실행되는데, 이렇게 가비지 컬렉션을 수행하기 위해 JVM 이 애플리케이션을 잠깐 멈추는 것을 "stop-the-world"라고 한다. 당연히 stop-the-world 시간이 길어지면 그만큼 쓰레드들이 멈춰 대기해야하므로 프로그램을 사용하는 입장에선 큰 성능 저하이다. GC에 있어 stop-the-world 현상은 피할 수 없어서, 성능 튜닝을 위해선 가능한 stop-the-world 시간(특히 Full GC/major GC 시간)을 줄이는 방법을 고려해야 한다. (자바의 신 저자 이상민님의 GC 튜닝 글 참고 https://d2.naver.com/helloworld/37111 )

 


JVM의 힙 영역

 

자바에서 new() 연산자 등으로 객체를 생성할 시, 생성된 객체는 힙에 저장된다.  또한 이 힙 영역에서는 Garbage Collcetion(GC)를 수행하여 더 이상 필요없는 객체의 메모리 할당을 제거한다.

 

JVM의 힙 영역(보다 정확하게는 HotSpot VM)은 다음과 같은 두 전제 하에 설계되었다.

  1. 새로 생성된 객체는 대부분 금방 죽는다.(= 참조를 잃어버린다. 유효한 참조가 없는 객체를 "unreachable한 객체"라고 하며, GC의 대상이 된다). 반면, 오래 살아 남은 객체는 기대 수명이 훨씬 길다. 고로 젊은 객체와 늙은 객체를 따로 떼어 놓는 것이 효율적이다.
  2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다. (JVM에선 이러한 참조가 존재할 경우를 위해 512 바이트의 카드 테이블이 Old 영역에 존재한다).

 

이러한 전제를 고려하여 힙 영역은 크게 Young 영역과 Old 영역으로 구성되어 있으며(원래는 이 두 영역 외에도 permanant 영역이 있었으나, java 8 이후로 제거. https://johngrib.github.io/wiki/java8-why-permgen-removed/ 참고), Young 영역은 다시 Eden과 Survivor 영역으로 나뉜다.

 

각 영역별 특징은 다음과 같다.

 

Young 영역 (Young Generation)

  • 새로 생성된 젊은 객체들이 저장되는 영역. 대부분의 객체들이 이 영역에서 죽어버리고 많은 객체들이 또 생성되기 때문에, GC와 stop-the-world가 자주 일어난다. stop-the-world가 빈번히 발생되기 때문에 GC가 그만큼 빨리 수행되어야 한다. 이 영역에서 발생하는 GC 를 minor GC라고 한다. minor GC가 발생하면 Eden영역과 Survivor 영역 둘 다 검사해서, unreachable한 객체들을 없앤다. minor GC에 걸리는 시간은 1초 이내이다.
  1. Eden 
    • 처음 생성된 객체가 할당 받는 영역이다. GC가 빈번히 일어나며, 대부분의 객체가 죽는 장소이기도 하다.  minor GC가 수행되고 살아남은 객체들은 survivor 영역 두 곳 중 한 곳으로 이동하게 된다. 
  2. Survivor
    • 어원 그대로, Eden 영역에서 GC 가 한 번 발생하고 "살아남은" 객체들이 저장되는 장소이다. from / to 라고 불리는 두 개의 영역으로 존재하며(여기서는 편의상 survivor 1, 2로 부른다), 한 곳은 반드시 비어있어야 한다. 두 곳 중 한 곳이 가득 차게되면, minor GC가 발생하고 살아남은 객체는 또다른 한 곳으로 이동하게 된다.  예로 들어 survivor 1에 객체가 가득 차게 되면, GC를 한번 수행하고 살아남은 객체들은 survivor 2로 이동하며 age가 증가하게 된다. 당연히 survivor 1은 빈 상태가 되며, GC가 수행되고 살아남은 객체들은 survivor 2에 차곡차곡 저장된다. 이렇게 계속 반복하여 GC를 수행하다가, 일정 age 이상이 되는 객체는 Old 영역으로 이동하게 된다. (이를 "Promotion" 승진이라 한다.)

 

Old 영역 (Old Generation)

  • 일정 age(generational count) 이상이 되는 객체들을 저장하는 영역. 이 영역 또한 타 영역들과 마찬가지로 가득 차게 될 시 GC를 수행한다. 여기서 수행되는 GC를 major GC 라고 한다. 대부분의 객체들은 young generation에서 죽고 old generation까지 넘어오는 일이 적기 때문에 major GC가 자주 일어나지는 않는다. 하지만 young 영역에 비해 old 영역은 메모리가 훨씬 크고 GC가 일어날 시 그만큼 많이 객체들을 검사해야 하므로, major GC는 기본적으로 minor GC 보다 훨씬 느리다. 그만큼 stop-the-world 시간도 길고. 따라서 major GC 시간을 줄이기 위한 GC 알고리즘들이 많이 존재한다.

 

참고로, Full GC 는 힙 영역 전체에 대한 GC를 의미한다.

 


Garbage Collection 과정

 

1. 새로 생성되는 객체들이 Eden 영역에 저장된다.

2. Eden 영역이 다 찰 시, minor GC를 수행하고 살아남은 객체들만 age를 증가시키며 Survivor 영역으로 보낸다.

3. Survivor 영역이 다 차게 되면 minor GC를 수행하고 살아남은 객체들만 age를 증가시키며 비어 있는 또 다른 Survivor 영역으로 보낸다. 기존 Survivor 영역은 비운다.

4. 이 과정을 반복하다가 특정 객체의 age가 일정 수준을 넘어서게 되면, 이 객체를 old 영역으로 이동시킨다(Promotion).

5. old 영역이 다 차게 될 시, major GC를 수행한다.

 

 

그림으로 보면 다음과 같다.

 

1. 새로 생성되는 객체들이 Eden 영역에 저장된다.

 

 

2. Eden 영역이 다 찰 시, minor GC를 수행하고 살아남은 객체들만 age를 증가시키며 Survivor 영역으로 보낸다.

 

 

3. Survivor 영역이 다 차게 되면 minor GC를 수행하고 살아남은 객체들만 age를 증가시키며 비어 있는 또 다른 Survivor 영역으로 보낸다. 기존 survivor 영역은 비운다.

 

 

4. 이 과정을 반복하다가 특정 객체의 age가 일정 값을 넘어서게 되면, 이 객체를 old 영역으로 이동시킨다(Promotion).

 

 

5. old 영역이 다 차게 될 시, major GC를 수행한다.

 

 


 

참고

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

2. https://perfectacle.github.io/2019/05/07/jvm-gc-basic/

3. https://www.youtube.com/watch?v=vZRmCbl871I 

4. https://mirinae312.github.io/develop/2018/06/04/jvm_memory.html

5. https://d2.naver.com/helloworld/329631

더 읽어보면 좋은 글

G1GC : https://johngrib.github.io/wiki/java-g1gc/