Image
Machine/JVM

JVM에서 Garbage Collection(GC) 이 일어나는 방식 알아보기

JVM에서 Garbage Collection이 중요한 이유

JVM은 자동으로 메모리를 관리해주기 때문에 GC(Garbage Collection)가 성능상 매우 중요하다. 모든 머신들이 그렇듯 JVM 또한 사용되지 않는 객체들이 제때 메모리에서 정리되지 못하고 한 번에 정리되거나 한다면 앱이 버벅거리거나, 제대로 동작하지 않을 수 있다. 또한 만약 사용되지 않는 객체가 GC의 대상이 되지 못한다면 Out of Memory Error 로 인해 앱이 강제 종료될 수도 있다. 

 

여기서 말하는 Memory는 Heap 영역이다. Stack 영역은 포인터만 저장하는 비교적으로 가벼운 저장 공간이기 때문에 성능 상 큰 이슈가 발생할 가능성이 적다.

 

GC가 일어나는 방식과 Heap Memory

JVM은 Heap 메모리 관리를 매우 논리적으로 한다. 한 번에 모든 메모리가 정리되어 앱이 버벅이는 것을 방지하기 위해 메모리를 크게 두 영역으로 나눈다. 하나는 Young Generation 영역이고 다른 하나는 Old Generation 영역이다. 

 

그림1. Heap 공간의 Young Generation과 Old 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라 불린다.

 

이제 Heap 메모리의 두가지 영역에 대해 알았으니 이 두가지 영역에서 자세히 어떤 일이 일어나는지 알아보도록 하자.

 

 

Young Generation

 

그림2. Young Generation

 

Young Generation은 [그림2]와 같이 Eden Space, Survivor Space0 ,Suvivor Space1 3가지 영역으로 나누어진다. 모든 객체는 시작점에서는 Eden 영역에서 생성되며, 점차 Survivor0, Survivor 1로 점차 이동한다.

 

 

Eden Space

 

그림3. Eden 영역

 

위에서 언급했듯이 Eden Space는 모든 객체가 거쳐가는 공간이다. 객체가 생성되면 무조건 Eden Space에 생성된다. Eden 영역은 모든 개체가 거쳐가는 공간이기 때문에 Survivor 영역들보다 기본적은 크게 설정되어 있다. 물론 이 값은 조절 가능하다. [그림3]은 4개의 객체가 Eden Space에 생성되는 것을 표현한다.

 

*이번 글에서는 [그림3의] 4개 객체가 Eden 영역에서 생성된 후 바로 모든 레퍼런스를 잃었다고 가정한다. 레퍼런스가 남아있으면 GC 대상이 되지 않기 때문이다.

 

그림4. Eden 영역에서의 GC

 

Eden 영역에서 Minor GC가 일어나면 Eden 영역에서 생성된 객체 중 Stack에서의  일부가 메모리에서 제거된다. 제거되는 방법은 랜덤이기 때문에 어떤 객체가 제거될지는 알 수 없다. 

 

 

Survivor0, Survivor1 Space

 

그림5. Survivor 영역

 

Eden 영역에서 일어난 Minor GC로부터 살아남은 객체들은 [그림5]와 같이 Survivor0 영역으로 이동한다. 

 

그림6. Survivor0 영역의 GC

 

만약 Survivor0 영역에서 GC가 일어났는데 객체가 또 살아남는다면 Survivor1 영역으로 이동한다.

 

그림7. Survivor1 영역

 

여기까지 했으면 대략적으로 객체가 어떻게 이동하는지 알았을 것이다. 만약 Young Generation의 마지막 영역인 Survivor1 영역의 GC에서도 살아남는다면 객체는 Old Generation 영역으로 넘어가게 된다.

 

 

Old Generation

 

그림8. Old Generation

 

Old Generation는 Young Generation에 비해 매우 큰 공간이다.

*Old Generation의 크기 또한 옵션을 통해 임의적으로 조절 할 수 있다. 

 

Old Generation에서는 Major GC가 일어난다. Major GC는 매우 큰 공간이기 때문에 데이터를 지우는데 많은 시간이 걸린다. 또한 Major GC가 일어나면 Thread가 멈추고 Mark and Sweep 작업을 해야 해서 CPU에 부하를 주기 때문에 일부 Major GC가 자주 일어나는 앱들에서는 GC가 일어날 때마다 멈추거나 버벅이는 현상이 발생한다. 특히 JVM 기반 서버에서 Major GC가 자주 일어나게 되면 GC가 일어날 때마다 서버 장애가 일어나기 때문에 매우 치명적이다.

 

따라서 최대한 Major GC는 일어나지 않도록 하는 것이 좋다. 제때 레퍼런스를 해제하고, 오래 붙잡는 객체를 최소화해야 이러한 장애 상황을 최소화 할 수 있다. 

 

 

 

반응형

 

이 글의 저작권은 '조세영의 Kotlin World' 에 있습니다. 글, 이미지 무단 재배포 및 변경을 금지합니다.

 

 

Kotlin, Android, Spring 사용자 오픈 카톡

오셔서 궁금한 점을 질문해보세요!
비밀번호 : kotlin22

open.kakao.com