Java

[Java] Garbage Collector

딸기케잌🍓 2020. 5. 11. 16:46

Java에서는 new키워드를 사용하여 동적으로 객체를 생성하면 heap영역에 저장됩니다.

새로 생성한 메모리의 해제는 JVM의 Garbage Collector(GC)가 수행하며 GC의 동작원리에 대해서 알아보겠습니다.

 

Garbage Collector 역할

Java의 GC는 다음의 역할을 수행한다고 볼 수 있습니다.

  1. 힙(heap)내의 객체 중에서 참조할 수 없는 가비지를 찾는다.
  2. 찾아낸 가비지를 처리해서 힙의 메모리를 회수한다.

 

 

가비지 컬렉션 과정

GC는 아래 두가지 전제 조건 하에 발생합니다.

  • 대부분의 객체는 금방 접근 불가능 상태가된다.
  • 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.

위 두가지 가설을  weak generational hypothesis 이론이라고 합니다.

 

 

GC가 수행되는 물리적 공간은 Young 영역, Old영역으로 나뉩니다.

Permanent 영역은 Method Area라고도 합니다.

이 영역에서 GC가 발생할 수도 있는데, 여기서 GC가 발생해도 Major GC의 횟수에 포함됩니다.

 

stop-the-world

stop-theworld란 GC(Garbage Collector)실행을 위해 JVM이 어플리케이션 실행을 멈추는 것입니다.

stop-the-world가 발생하면 GC를 실행하는 스레드 이외의 나머지 스레드는 모두 작업을 멈춥니다.

GC작업을 완료한 이후에야 중단했던 작업을 다시 시작합니다.

필요한 이유

힙에서 개체를 재배치할 때 JVM은 이 개체에 대한 모든 참조를 수정해야함 재배치 과정에서 STW가 필요함

 

 

 

 

 

Young 영역

새롭게 생성한 객체의 대부분이 여기에 위치합니다.

대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라집니다.

이 영역에서 객체가 사라질 때 Minor GC가 발생한다고 말합니다.

Minor GC는 JVM을 멈추지 않고 진행합니다.

 

Young영역은 3개의 영역으로 구성됩니다.

  • Eden영역
  • Survivor 영역 2개

 

Young 영역에서의 GC 과정

Eden 영역에 최초로 객체가 만들어지고, Survivor 영역을 통해서 Old 영역으로 오래 살아남은 객체가 이동합니다.
  1. 새로 생성한 대부분의 객체는 Eden에 위치한다.
  2. Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor영역 중 하나로 이동한다.
  3. Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor영역으로 객체가 계속 쌓인다.
  4. 하나의 Survivor 영역이 가득 차게 되면 그중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득찼던 Survivor 영역은 아무 데이터도 없는 상태로 된다.
  5. 이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게 된다.

-->Survivor 영역 중 하나는 반드시 비어 있는 상태로 남아 있어야 한다.

두 Survivor 영역에 모두 데이터가 존재하거나, 두 영역 모두 사용량이 0이라면 정상적인 상황이 아닙니다.

Young영역에서 Old영역으로 이동하는 기준이 되는 값은 -XX:MaxTenuringThreshold 옵션으로 설정합니다. 이 설정값을 넘어가면 Young 에서 Old 영역으로 이동합니다.

 

 

HotSpot VM에서는 빠른 메모리 할당을 위해 다음 두 가지 기술을 사용합니다.

  • bump-the-pointer
  • TLABs(Thread-Local Allocation Buffers)

 

bump-the-pointer

Eden 영역에 할당된 마지막 객체를 추적합니다. 마지막 객체는 Eden 영역의 맨 위(top)에 있습니다. 그리고 그 다음에 생성되는 객체가 있으면, 해당 객체의 크기가 Eden 영역에 넣기 적당한지만 확인후 적당하면 Eden 영역에 넣게 되고, 새로 생성된 객체가 맨 위에 있게 됩니다. 따라서, 새로운 객체를 생성할 때 마지막에 추가된 객체만 점검하면 되므로 매우 빠르게 메모리 할당이 이루어집니다.

 

멀티 스레드 환경에서는 Thread-Safe하기 위해서 만약 여러 스레드에서 사용하는 객체를 Eden 영역에 저장하려면 락(lock)이 발생할 수 밖에 없고, lock-contention 때문에 성능은 매우 떨어지게 될 것입니다.

이를 해결한 것이 TLABs입니다.

 

 

TLABs(Thread-Local Allocation Buffers)

각각의 스레드가 각각의 몫에 해당하는 Eden 영역의 작은 덩어리를 가질 수 있도록 하는 것입니다. 각 쓰레드에는 자기가 갖고 있는 TLAB에만 접근할 수 있기 때문에, bump-the-pointer라는 기술을 사용하더라도 아무런 락이 없이 메모리 할당이 가능합니다.

 

 

 

Old 영역

Young영역에서 살아남은 객체가 여기로 복사됩니다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young영역보다는 GC는 적게 발생합니다.

이 영역에서 객체가 사라질 때 Major GC(Full GC)가 발생한다고 말합니다.

이 때는 JVM이 동작을 멈춥니다. 그러므로 메이저 GC의 발생과 처리시간을 줄이는 것이 중요합니다.

Old영역은 데이터가 가득 차면 GC를 실행합니다.

GC의 종류는 JDK 7을 기준으로 5가지가 있다.

 

Serial GC(-XX:+UseSerialGC)

mark-sweep-compact 알고리즘을 사용하여 GC를 수행합니다.

  • mark - old영역에서 살아 있는 객체를 식별(mark)한다.
  • sweep - mark 된 Object를 제외하고 제거합니다.
  • compact - 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다. Sweep 이후 비어있는 Heap 공간들을 연속되게 쌓이도록 힙의 앞 부분부터 채우는 과정입니다.

 

Serial GC는 적은 메모리에 적합한 방식이며, 데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식입니다. Serial GC를 사용하면 애플리케이션의 성능이 많이 떨어집니다. -> 운영 서버에서 사용하면 안되는 방식입니다.

 

Parallel GC (-XX:+UseParallelGC)

Parallel GC는 Serial GC와 기본적인 알고리즘은 같지만 Parallel GC는 GC를 처리하는 쓰레드가 여러개입니다.

그러므로 Serial GC보다 빠르게 객체를 처리할 수 있으며 Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리합니다.

 

Parallel Old GC(-XX:+UseParallelOldGC)

Parallel GC와 비교하여 Old영역의 GC알고리즘만 다릅니다.

Mark-Summary-Compaction단계를 거칩니다.

Summary단계는 앞서 GC를 수행한 영역에 대해서 별도로 살아 있는 객체를 식별한다는 점에서

Mark-Sweep-Compaction알고리즘의 Sweep단계와 다르며, 약간 더 복잡한 단계를 거칩니다.

 

CMS GC (-XX:+UseConcMarkSweepGC)

Initial Mark

클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는다. -->멈추는 시간은 매우 짧다.

 

Concurrent Mark

방금 살아 있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다.

다른 스레드가 실행 중인 상태에서 동시 진행이 가능하다.

 

Remark

Concurrent Mark단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다.

 

Concurrent Sweep

쓰레기를 정리하는 작업을 한다.

다른 스레드가 실행되고 있는 상황에서 진행한다.

 

-->stop-the-world시간이 매우 짧고, 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용한다.

 

단점

다른 GC보다 메모리와 CPU를 더 많이 사용한다.

Compaction 단계가 기본적으로 제공되지 않는다.

-->

조각난 메모리가 많아서 Compaction작업을 실행하면 다른 GC방식보다 더 오래 걸리기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 한다.

 

 

G1(Garbage First) GC

CMS GC를 대체하기 위해서 만들어진 GC로 바둑판 모양의 영역에 객체를 할당하고 GC를 실행한다.

리전 개념을 도입하여 힙을 균등하게 여러 지역으로 나누고 각 지역을 역할과 함께 논리적으로 구분하여(eden, survivor,old) 객체를 할당함, 가비지가 많은 지역에 대해 우선적으로 GC를 수행함

 

minor gc

해당 영역이 꽉 차면 다른 영역에 객체를 할당하고 GC를 실행한다. 지역을 추적하고 있기 때문에 가바지가 가장 많은 지역을 찾아서 mark and sweep 수행

살아남은 객체는 availabe/unused 지역으로 이동됨

 

major gc

어느 영역에 가비지가 많은 지를 알고 있으므로 해당 지역에서만 gc수행

concurrent하게 수행되므로 앱 지연도 최소화

 

 

Java 9부터 디폴트 GC이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

'Java' 카테고리의 다른 글

[Java] 컴파일러 vs 인터프리터 vs JIT compiler  (0) 2021.05.31
[Java] JVM 구조와 원리  (0) 2021.05.23
[Java] Static 변수, static 메소드  (0) 2021.05.23
[Java] String, StringBuffer, StringBuilder  (0) 2021.04.22
[Java] JavaAgent  (0) 2021.01.17