[JVM] Garbage Collector

2024. 4. 18. 23:51자바

GC(Garbage Collector)란 ?

Java Application은 필요에 따라서 메모리에서 객체를 얻습니다.
애플리케이션이 작성된 로직을 따라 진행되면서 메모리에 계속해서 새로운 객체들의 정보가 올라갈 것이고,

애플리케이션에서 더 이상 사용하지 않는 객체에 대한 정보도 메모리에 올라가 있을 것입니다.


이처럼 애플리케이션의 메모리에 정보가 계속 올라가는데, 저장 공간은 무한하지 않기 때문에 메모리가 가득 차게 된다면

OOM Error(OutOfMemoryErrors)로 인해서 애플리케이션이 비정상적으로 종료가 될 것입니다.

이를 방지하기 위해서 GC가 애플리케이션에서 동작합니다.

 

애플리케이션에서 더이상 사용하지 않아 참조되지 않는 객체를 Garbage라고 합니다.
GC는 메모리 관리를 위해 사용중인 객체와 사용하지 않는 객체를 식별해 Garbage를 자동으로 결정하고 사용하지 않는 객체를 삭제해 메모리 공간을 회수해 역할을 수행합니다.

 

JVM의 GC는 자동 프로세스이기 때문에 Java 개발자는 사용되지 않는 메모리의 객체를 명시적으로 해제해줘야 하는 부담이 없습니다.


만약 명시적으로 해제해주고 싶다면

Student student = new Student();
student.setName("김영범");
student = null;

System.gc();

위의 코드처럼 객체에 null로 지정하거나 System.gc()를 사용해 GC를 호출할 수 있습니다.

(System.gc()를 호출하는건 시스템의 성능에 매우 큰 영향을 미치므로 사용하면 안됨)

 

 

 

GC의 구조와 처리과정

Reachability

Java GC는 객체가 Garbage인지 판별하기 위해서 reachability라는 개념을 사용합니다.

어떤 객체에 유효한 참조가 있으면 ‘reachable’로 없으면 ‘unreachable’로 구별하고, unreachable 객체를 Garbage로 간주해 GC를 수행합니다.


GC의 구조

GC는 두가지 가설 하에 만들어졌습니다.

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

이러한 가설을 weak generational hypothesis라 합니다. 이 가설의 장점을 최대한 살리기 위해서 HotSpot JVM에서는
크게 Young Generation, Old Generation 2개로 물리적 공간을 나누었습니다.


Hotspot JVM

Hotspot JVM은 미국의 Longview Technologies LLC라는 회사에서 1999년에 처음 발표된 JVM입니다.

이후 이 회사는 같은 해 SUN에 의해 인수되어 1.3버전부터 SUN의 기본적인 JVM이 되었습니다.

Hotspot JVM은 현재 가장 일반적인 JVM 중의 하나로 Windows, Linux, Solaris는 물론 Mac OS와 기타 UJnix OS에도 탑재가 가능합니다. HP Unix에서도 Hotspot JVM을 제공하고 있고, 심지어 Oracle을 설치할 때 기본적으로 설치되어 구동되는 JVM 조차 Hotspot JVM입니다.

따라서 Sun, Oracle, HP, Windows, Linux, MacOS에서 제공하는 JVM은 Hotspot JVM으로 명명하고 IBM에서 제공하는 JVM은 IBM JVM이라 부릅니다.


Young Generation과 Old Generation

  • Young Generation 
    새롭게 생성한 대부분의 객체가 여기에 위치합니다.
    대부분의 객체가 금방 접근 불가능한 상태가 되기 때문에 많은 수의 객체들이 이 영역에서 생성되었다가 사라집니다.

    Young Generation에서 GC가 동작하는 것을 Minor GC라고 합니다.

  • Old Generation
    접근 불가능한 상태가되지 않아 Young Generation에서 살아남은 객체가 위치.
    Young Generation보다 GC가 적게 발생합니다.

    Old Generation에서 GC가 동작하는 것을 Major GC라고 합니다.

Young Generation

Young Generation은 다시 Survivor 영역과 Eden 영역으로 나뉩니다.

  • Eden영역은 객체가 Heap에 최초로 할당되는 장소입니다.
  • Survivor 영역은 Eden 영역에서 넘어온 객체들이 머무르는 장소입니다.
  • Survivor 영역은 두 개의 영역(Survivor1, Survivor2)으로 이루어져 있습니다.
  • Young Generation의 처리 절차
    1. Eden 영역이 꽉 차게 되면 객체의 참조 여부를 확인
    2. 참조가 되어있는 객체라면 Survivor 영역으로 넘기고 참조가 되어있지 않은 Garbage라면 그냥 남겨 놓음
    3. 참조되어 있는 모든 객체가 Survivor 영역으로 넘어가면 Eden 영역을 모두 청소
    4. Survivor 영역도 꽉 차게 되면 GC가 작동해서 참조되지 않은 객체를 다른 Survivor 영역으로 옮김
    5. 다시 한 쪽의 Survivor가 가득 차면 참조된 객체를 다른 Survivor에 옮기고 GC가 작동
    6. 살아남은 객체들이 다른 Survivor 영역으로 갈 때 객체들의 나이가 올라감
      (따라서 Survovir의 영역 중 한 곳은 반드시 비어있는 상태)
    7. 이러한 과정을 반복해 Survivor 영역에서 오래 살아남아 일정 나이 이상이 된 객체들은 Old Generation으로 이동

아래의 그림으로 설명을 확인해 보자.

 

(참조정보) Hbump-the-pointer, TLABs(Thread-Local Alocation Buffers)

HotSpot VM에서는 보다 빠른 메모리 할당을 위해서 두 가지 기술을 사용합니다.
하나는 bump-the-pointer라는 기술이며, 다른 하나는 TLABs(Thread-Local Allocation Buffers)라는 기술입니다.


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

그러나 멀티 스레드 환경을 고려하면 이야기가 달라집니다. 만약 여러 스레드에서 사용하는 객체를 Eden 영역에 저장하려면 Thread-Safe하기 위해서 락(lock)이 발생할 수 밖에 없고, lock-contention 때문에 성능은 떨어지게 될 것입니다.
(lock-contention: 여러 스레드가 자원에 접근하려 할 때 해당 자원에 대한 lock를 얻기위해 대기 하는 것)

HotSpot VM에서 이를 해결한 것이 TLABs입니다.

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

Old Generation

Young Generation에서 오래 살아남아 일정 Age이상이 된 객체는 Promotion이 발생해 Old Generation 영역으로 이동합니다.
Old Generation 영역이 채워지다가 가득차게 되면 Young Generation 에서 처럼 GC가 동작합니다.

 

 

 

GC 방식

Old Generation에서는 GC의 방식에 따라 처리 절차가 달라집니다.
JVM의 버전에 따라 여러가지 GC 방식이 추가되고 발전되었는데, 버전별로 지원하는 GC는 차이가 존재합니다.

 

Serial GC

Young 영역에서의 GC는 앞에서 설명한 방식을 사용하고 Old 영역에서의 GC는 Mark-sweep-compact이라는 알고리즘을 사용합니다.
해당 알고리즘을 단계별로 설명하면 아래와 같습니다.

  • Old 영역에 살아있는 객체를 식별(Mark)
  • Heap의 앞부분부터 확인하여 살아 있는 객체만 남긴다(Sweep)
  • 각 객체들이 연속되게 쌓이도록 Heap의 가장 앞부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다.(Compact)

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


Parallel GC

Parallel GC는 Serial GC와 기본적인 알고리즘은 같습니다.
Serial GC는 처리하는 쓰레드가 하나인 것에 비해 Parallel GC는 Miner GC, Major GC 모두 멀티 쓰레드를 사용해 처리합니다.


CMS GC

  • 초기 Initial Mark 단계에서 참조 상태인 객체를 짧은 시간에 Marking 한다.
  • Concurrent Mark 단계에서 방금 살아있다고 확인한 객체에서 참조하고 있는 객체를 따라가면서 확인한다.
    (이 단계의 특징은 다른 쓰레드가 실행 중인 상태에서 동시에 진행된다는 점)
  • Remark 단계에서는 Concurrent Mark 단게에서 새로 추가되거나 참조가 끊긴 객체를 확인한다.
  • Concurrent Sweep 단계에서는 Garbage를 정리하는 작업을 실행한다.

CMS GC는 stop-the-world 시간이 매우 짧다는 장점이 있어 모든 애플리케이션의 응답 속도가 매우 중요할 때 사용합니다.


반면에 단점으로는 아래의 2가지가 있습니다.

  • 다른 GC 방식보다 메모리와 CPU를 더 많이 사용
  • 메모리 파편화

G1 GC

사진에서 보이듯이 G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행합니다.
기존의 Young, Old 영역의 개념과 다른 Resion이라는 개념을 도입한 방식입니다.
영역이 꽉 차게 되면 다른 영역에서 객체를 할당하고 GC를 실행합니다.

앞서 설명했던 Young 영역에서 Old 영역으로 이동하는 단계가 사라진 GC 방식이라고 이해하면 됩니다.

  • CMS와 비슷한 방식으로 동작 시작
  • Heap에 전역적으로 Marking
  • 가장 많은 공간이 있는 곳부터 메모리 회수를 진행

CMS GC의 CPU 리소스 및 메모리 파편화의 단점을 해결하였습니다.

'자바' 카테고리의 다른 글

빌드(Build)  (0) 2024.06.17
Optional  (0) 2024.04.24
[JVM] Class Loader  (0) 2024.04.18
[JVM] Runtime Data Area  (0) 2024.04.18
[JVM] JVM 이란  (0) 2024.04.18