[Java] 가비지 컬렉션(Garbage Collection)이란?

jjuny7712·2023년 12월 16일
0

가비지 컬렉션(Garbage Collection)은 무엇일까?

메모리 관리 기법 중 하나로, 프로그래머가 동적으로 할당한 메모리 영역 중 더 이상 쓰이지 않는 영역을 자동으로 찾아내어 해제하는 기능이다.

C언어에서는 free() 함수를 통해 개발자가 명시적으로 메모리를 해제 해주었지만, 자바(Java)에서는 GC(Garbage Collection)가 자동적으로 메모리를 해제해줘서 개발자는 개발에만 집중할 수 있다.

물론 Java에서도 메모리를 명시적으로 지정하여 해제하지 못하는건 아니다. 객체에 null을 할당하거나, System.gc()메소드를 호출해서 메모리를 해제할 수 있다. 단, System.gc() 메소드를 호출하는건 시스템의 성능에 매우 큰 영향을 미치므로 일반적으로 권장하지 않는다.

왜 System.gc()는 시스템 성능에 안좋을까?

  1. JVM은 GC를 효율적으로 수행하기 위해 여러 알고리즘을 사용하고, 이 알고리즘은 시스템의 상태 및 프로그램의 동작에 따라 동적으로 조절된다. 하지만, System.gc()를 호출하면 JVM이 선택한 알고리즘을 무시하고, 강제로 GC를 실행하게 되기 때문에 성능이 좋지 않다.
  1. System.gc()는 GC를 실행하는 스레드를 제외한 다른 스레드를 멈춰버릴 수 있다. (GC 종류에 따라 다르긴 하지만) 대규모 시스템에서는 다수의 스레드가 병렬로 실행되는데, 이러한 스레드들이 멈추면 전체 성능이 저하될 가능성이 있기 때문에 권장하지 않는 것이다.

  2. System.gc()로 가비지 컬렉션을 호출하면 JVM은 불필요한 가비지 컬렉션을 수행할 수 있다. 이로 인해 애플리케이션 성능이 감소할 수 있으며, 실제로 필요한 경우보다 더 자주 가비지 컬렉션이 발생할 수 있다.

특별한 상황이나 튜닝이 필요한 경우를 제외하고는 시스템이 자동으로 가비지 컬렉션을 실행하도록 냅두는 것이 좋다. (우리가 시니어가 아닌 이상 손대지 않는게 좋을거같다)

GC 동작 방법

Stop The World(STW)

GC를 실행하면 JVM은 애플리케이션 실행을 멈춘다. 이것을 stop-the-world라고 하는데, stop-the-world가 발생하면 GC를 실행하는 스레드를 제외한 나머지 스레드는 모두 작업을 멈춘다.

GC 튜닝의 목표는 stop-the-world 시간을 줄이는 것이다. (이로 인해 JVM 버전에 따라 GC가 발전해왔다)

왜 GC를 실행하면 Stop The World가 발생할까?

스레드가 일시 중단되는 이유는 가비지 컬렉션 작업을 수행하기 위해 힙 영역에 대한 일관성을 유지하고, 안전하게 가비지 컬렉션을 수행하기 위함이다.

애플리케이션 스레드가 멈추어야 현재 메모리 상에서 살아있는 객체를 정확히 식별할 수 있다.

Mark And Sweep

Mark는 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업이다.

Sweep은 Mark 단계에서 식별한 사용되지 않는 메모리를 해제하는 작업이다.

GC는 'weak generational hypothesis' 라는 가설을 기반으로 만들어졌다.

객체는 대부분 일회성이며, 메모리에 오랫동안 남아있는 경우는 드물다는 것이다.

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

Reachable은 객체가 참조되고 있는 상태, Unreachable은 객체가 참조되고 있지 않은 상태(GC의 대상이 됨)를 의미한다.

이러한 가설로 인해 JVM의 GC가 수행되는 Heap 메모리 영역은 2개의 물리적 공간으로 나뉜다.

Young Generation 영역Old Generation 영역이다.

초기에는 Perm 영역도 존재했지만, Java8부터는 삭제되었다고 한다.

Young 영역(Young Generation)

새롭게 객체가 할당되면 Young 영역에 저장된다. weak generational hypothesis 가설로 대부분의 객체는 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다.

Young 영역에서 발생하는 GC를 Minor GC라고 한다.

Young 영역은 3개의 영역으로 나뉜다.
1. Eden 영역
2. Survivor 영역(2개)

Young 영역에 새로 할당된 객체가 저장된다고 했는데 사실은 Eden 영역에 저장되는 것이다. Eden 영역에 저장되었다가 Eden 영역이 가득 차면 Minor GC가 실행되는데, 사용되지 않는 메모리는 해제되고 살아남은 객체는 Survivor 중 한 곳으로 이동한다.

Survivor은 2개의 영역으로 되어있지만, 반드시 1개의 영역에만 데이터가 존재하며, 하나의 Survivor 영역이 가득 차면 다른 Survivor 영역으로 모두 옮겨진다.(이때, 기존 Survivor 영역은 비게 된다)

이러한 과정을 반복하다가, 계속해서 살아남아 있는 객체는 Old 영역으로 이동한다.

그럼 언제 Old 영역으로 이동할까?

Minor GC에서는 살아남은 횟수를 기록하는 age가 있으며(age는 JVM의 Object Header에 기록된다.) Minor GC가 실행될 때 마다 1이 증가한다.
여기서 MaxTenuringThreshold 값이 설정되어 있는데(GC 종류 마다 다르다) 이 설정값을 초과하면 Old 영역으로 객체가 이동하는 것이다. 단, MaxTenuringThreshold를 초과하지 않아도 Survivor 메모리가 부족할 경우 Old로 옮겨질 수 있다.

Old 영역(Old Generation)

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

Old 영역에서 발생하는 GC를 Major GC라고 한다. (추가로 전체 힙 영역을 대상으로 하는 가비지 컬렉션을 Full GC라고 한다)

Old 영역이 가득 차면 Major GC가 발생한다.

Old 영역에 있는 객체가 Young 영역을 참조할때는?

Minor GC를 실행할때 Young 영역을 보면서 참조하지 않는 객체를 삭제시키는데, Old 영역에 있는 객체가 Young 영역의 객체를 참조할 수도 있다. 추가 장치가 없으면 참조되는 객체를 삭제할 수도 있다.

이것을 방지하기 위해 카드 테이블(Card Table)을 둔다. 만약 카드 테이블이 없다면 Minor GC가 실행될때도 Old영역도 보면서 Young 영역에 있는 객체를 참조하는지 검사해야 한다.(상당히 비효율적이다)

그렇기에 Minor GC가 실행될 때 (Young 영역 + 카드 테이블)을 탐색한다.

Old 영역의 객체가 Young 영역의 객체를 참조한다면 카드 테이블에 체크한다. 결국 카드 테이블은 Minor GC를 빠르게 할 수 있도록 하는 장치이다.

Minor GC vs Major GC

GC의 단점

  1. 메모리가 언제 해제되는지 정확하게 알 수 없어 제어하기 힘들다.
  2. GC가 실행되면 다른 스레드가 멈추기 때문에 오버헤드가 발생한다.

다양한 GC 알고리즘

GC는 Stop The World라는 애플리케이션이 중지되는 문제가 있다. 또한, JVM의 Heap 사이즈가 커지면서 GC를 최적화 하기 위해 다양한 가비지컬렉션 알고리즘이 개발되었다.

종류는 다음과 같다.

  • Serial GC
  • Parallel GC
  • Parallel Old GC
  • CMS GC (Concurrent Mark Sweep)
  • G1(Garbage First) GC
  • Shenandoah GC
  • ZGC (Z Garbage Collector)

Serial GC

서버의 CPU 코어가 1개일 때 사용하기 위해서 만든 방식으로 성능이 좋지 않다.

Minor GC는 Mark Sweep 알고리즘을 사용하는 건 동일하지만, Old 영역에서 실행되는 Major GC는 Mark Sweep Compact라는 알고리즘을 사용한다.

Mark Sweep Compact 알고리즘

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

Serial GC 실행 명령어

java -XX:+UseSerialGC -jar Application.java

-XX:+UseSerialGC 옵션을 지정하여 이 명령어를 통해 Serial GC 알고리즘으로 Heap 메모리를 관리하도록 할 수 있다.

Parallel GC

Java 8의 디폴트 GC이다.

Serial GC와 기본적인 알고리즘은 같지만, Young 영역의 Minor GC를 멀티스레드로 수행한다. (단, Old 영역은 싱글스레드)

Parallel GC 실행 명령어

java -XX:+UseParallelGC -jar Application.java 
# -XX:ParallelGCThreads=N : 사용할 쓰레드의 갯수

GC 스레드는 기본적으로 cpu 개수만큼 할당된다. 다만, 옵션을 통해 스레드 갯수를 설정할 수 있다.

Parallel Old GC

JDK 5 update 6부터 제공한 GC방식이다.

Young 영역 뿐만 아니라, Old 영역에서도 멀티스레드로 GC를 수행하고, 새로운 Mark Summary Compact 방식으로 Old 영역도 멀티스레드로 처리한다.

Parallel Old GC 실행 명령어

java -XX:+UseParallelOldGC -jar Application.java
# -XX:ParallelGCThreads=N : 사용할 쓰레드의 갯수

CMS GC (Concurrent Mark Sweep)

애플리케이션의 스레드와 GC 스레드가 동시에 실행되어 Stop The World 시간을 최소화 하기 위해 고안된 GC이다.

단, GC 과정이 매우 복잡해지고 아래와 같은 단점이 존재한다.
1. GC 대상을 파악하는 과정이 복잡한 여러단계로 수행되기 때문에 다른 GC에 비해 CPU 사용량이 높다.
2. 메모리 파편화 문제가 있다.

CMS GC는 Java9부터 deprecated 되었고, Java14에서는 사용 중지 되었다.

CMS GC 실행 명령어

# deprecated in java9 and finally dropped in java14
java -XX:+UseConcMarkSweepGC -jar Application.java

G1(Garbage First) GC

G1 GC는 CMS GC를 대체하기 위해 jdk 7 버전에서 최초로 릴리즈된 GC이다. Java 9+ 버전의 디폴트 GC로 지정되었다.

G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다. (이 바둑판을 Region이라 한다) 기존 GC는 Young과 Old 영역으로 Heap 영역을 나누었지만 G1 GC에서는 전체 Heap 영역을 Region이라는 영역으로 분할했다.

Region (바둑판)이 꽉 차면 다른 영역에 객체를 할당하고 GC를 실행한다. 즉, Heap 영역을 전체 탐색하는 것이 아닌 영역을 나눠 탐색하고 영역별로 GC가 발생한다. 메모리가 많이 차있는 영역을 우선적으로 GC한다.

G1 GC 실행 명령어

java -XX:+UseG1GC -jar Application.java

Shenandoah GC

Java 12에서 릴리즈되었다. CMS의 단편화, G1의 pause 이슈를 해결했다고 한다.

강력한 Concurrency와 가벼운 GC 로직으로 Heap 사이즈에 영향을 받지 않고 일정한 pause 시간이 소요되는 것이 특징이다.

Shenandoah GC 실행 명령어

java -XX:+UseShenandoahGC -jar Application.java

ZGC (Z Garbage Collector)

Java 15에서 릴리즈되었고, 대량의 메모리를 로우 레이턴시로 잘 처리하기 위해 디자인된 GC이다. ZGC는 ZPage라는 영역을 사용하며(G1의 Resion은 크기가 고정이지만) ZPage는 2mb 배수로 동적으로 운영된다.

또한 Heap 크기가 증가하더라도 Stop The World의 시간이 절대 10ms를 넘지 않는다고 한다.

ZGC 실행 명령어

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar Application.java
profile
차곡차곡

0개의 댓글