GC(Garbage Collector)
동적으로 할당한 메모리 영역 중 사용하지 않는 영역을 탐지하여 해제하는 기능
GC 종류
- SerialGC
- 단일 스레드로 작동한다.
- JVM 옵션:
-XX:+UseSerialGC
- ParallelGC
- 여러 개의 스레드를 사용하여 Young Genertaion영역에서 가비지 컬렉션을 수행한다.
- JVM 옵션:
-XX:+UseParallelGC
- Parallel Old GC
- Parallel GC의 구세대 버전으로 Old Generation 영역에서 병렬로 가비지 컬렉션을 수행한다.
- JVM 옵션:
XX:+UseParallelOldGC
- JVM 옵션:
XX:+UseParallelOldGC
- Concurrent Mark Sweep(CMS) GC
- 낮은 중지 시간을 목표로 하는 멀티프로세싱 환경에 적합하다.
- JVM 옵션:
-XX:+UseConcMarkSweepGC
- G1(Grabage-First) Collector
- 큰 힙을 관리하고 예측 가능한 중지 시간을 제공하는 데 적합하다.
- 힙을 여러 개의 작은 영역으로 나누어 관리한다.
- JVM 옵션:
-XX:+UseG1GC
- ZGC(Z Garbage Collector)
- Java 11부터 사용 가능한 저지연(law latency) 가비지 컬렉터
- 대규모 힙과 멀티 프로세서 환경에서 중지 시간을 최소화하는 데 초점을 맞춘다.
- JVM 옵션:
XX:+UseZGC
- Shenandoah
- ZGC와 유사하게 저지연 가비지 컬렉션을 목표로한다.
- 대규모 힙에서도 짧은 중지 시간을 제공한다.
- JVM 옵션:
XX:+UseShenandoahGC
stop-the-world
GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다. stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다. GC 작업을 완료한 이후에야 중단해 두었던 작업을 다시 시작한다. 모든 GC 알고리즘은 stop-the-world가 발생한다. 대부분의 GC 튜닝은 stop-the-world의 시간을 줄이는 것이다.
GC에서 관리하는 JVM의 영역
- 힙(Heap): JVM에서 가장 큰 메모리 영역, 힙은 다음과 같은 세부 영역으로 나뉜다.
- Young Genertaion
- 새로 생성된 객체들이 할당되는 영역으로 대부분의 객체가 금방 사용하지 않게 되는 객체를 위한 곳이다. Eden영역과 Survivor 영역이 포함된다.
- 해당 영역에서 발생하는 GC를 Minor GC라고 하며 Major GC에 비해 빠르다.
- Old Generation
- Young에서 오래 살아남은 객체들이 이동되는 곳이다. 이 영역은 객체가 더 오래 존재한 곳이다.
- 해당 영역에서 발생하는 GC를 Major GC라고 하며 Minor GC에 비해 느리다.
- Permanent Generation Or Metaspace
- 클래스와 메소드에 대한 메타데이터가 저장되는 영역 PS. Java 8부터는 PerGen이 Metaspace로 대체되었다.
- Young Genertaion
- 스택(Stack): Java 메소드의 호출과 실행에 사용된다.
- 코드(Code): 프로그램의 바이트코드 즉 컴파일된 클래스와 인터페이스의 코드가 저장되는 영역
- 네이티브 메소드 스택(Native Method Stack): 자바 외의 언어로 작성된 네이티브 코드를 위한 스택
가비지 컬렉션은 힙 영역을 주 대상으로 관리하며 힙 내의 객체들 중에서 더이상 사용되지 않는 객체들을 식별하고 이를 수집하여 메모리를 해제한다. 다른 영역들은 각각 스레드나 JVM자체에 의해 관리가 되며 가비지 컬렉션의 대상이 아니다.
GC 과정
1. 새로운 객체가 Eden 공간에 할당된다. 두 Survivor 영역은 빈 공간으로 시작한다.
2. Eden 공간이 가득차면 Minor GC가 작동한다.
3. 참조된 객체는 첫번째 Survivor 영역으로 이동하고 참조가 없는 객체는 Eden 영역에서 제거한다.
4. 다음 Minor GC에서는 Eden 공간에서도 동일한(1~3) 작업이 발생합니다. 참조되지 않은 객체는 삭제되고 참조된 객체는 생존 공간으로 이동한다. 다만, 이 경우에는 두 번째 생존 공간(S1)으로 이동하고 첫 번째 생존 공간(S0)의 마지막 마이너 GC의 객체는 수명이 증가하고 S1으로 이동한다. 살아남은 모든 객체가 S1으로 이동하면 S0와 Eden 모두 지워진다. 이제 생존자 공간에는 서로 aged를 가지는 객체가 있는 것을 염두하자.
5. 다음 Minor GC 에서는 동일한 프로세스가 발생한다. 하지만, 이번에는 Survivor 영역이 전환된다. 참조된 객체는 S1(두번째 Survivor 영역)에서 S0(첫번째 Survivor 영역)으로 이동된다. 살아남은 객체들은 aged 된다.
6. 아래의 슬라이드에서는 프로모션을 보여준다. Minor GC 후에 오래된 객체가 특정 연령 임계값(해당 예에서는 8)에 도달하면 Young Genertaion에서 Old Generation로 전환된다.
7. Minor GC가 계속 발생하면 객체는 Young Genertaion에서 Old Generation으로 이동한다.
Java에서 GC 사용 예시
명령어
System.gc();
Runtime.getRuntime().gc();
이 명령어들은 JVM에게 가비지 컬렉션을 실행하도록 요청한다. 하지만, 실제로 가비지 컬렉션이 실행될 시기와 방식은 JVM의 가비지 컬렉터 구현에 달려있다.
JVM 옵션을 통한 가비지 컬렉터 설정
XX:+UseSerialGC
: 시리얼 가비지 컬렉터를 사용하도록 설정합니다.XX:+UseParallelGC
: 병렬 가비지 컬렉터를 사용하도록 설정합니다.XX:+UseParallelOldGC
: 구세대 영역에 대해 병렬 가비지 컬렉터를 사용합니다.XX:+UseConcMarkSweepGC
: CMS(Concurrent Mark-Sweep) 가비지 컬렉터를 사용합니다.XX:+UseG1GC
: G1(Garbage-First) 가비지 컬렉터를 사용합니다.
Ex)
java -XX:+UseG1GC -Xms512m -Xmx4g -XX:NewRatio=3 -XX:SurvivorRatio=6 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc-log.txt -jar application.jar
XX:+UseG1GC
: G1 가비지 컬렉터를 사용합니다.Xms512m
: JVM의 초기 힙 크기를 512MB로 설정합니다.Xmx4g
: JVM의 최대 힙 크기를 4GB로 설정합니다.XX:NewRatio=3
: 신세대와 구세대의 메모리 비율을 1:3으로 설정합니다.XX:SurvivorRatio=6
: 신세대 내의 에덴 영역과 서바이버 영역의 비율을 1:6으로 설정합니다.XX:+PrintGCDetails
,XX:+PrintGCDateStamps
: 가비지 컬렉션의 상세 정보와 타임스탬프를 콘솔에 출력합니다.Xloggc:gc-log.txt
: 가비지 컬렉션 로그를 'gc-log.txt' 파일에 기록합니다.
JVM옵션을 통한 가비지 컬렉션 튜닝
Xms<size>
: JVM의 초기 힙 크기를 설정합니다.Xmx<size>
: JVM의 최대 힙 크기를 설정합니다.Xmn<size>
: 신세대 영역의 크기를 설정합니다.XX:NewRatio=<ratio>
: 신세대와 구세대의 비율을 설정합니다.XX:SurvivorRatio=<ratio>
: 에덴 영역과 서바이버 영역의 비율을 설정합니다.XX:+PrintGCDetails
,XX:+PrintGCDateStamps
,Xloggc:<file>
: GC 로그를 상세하게 출력하거나 파일로 저장합니다.
Ex)
java -Xms512m -Xmx4g -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ScavengeBeforeFullGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc-log.txt -jar application.jar
java -Xms512m -Xmx4g -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ScavengeBeforeFullGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc-log.txt -jar application.jar
Xms512m
: JVM의 초기 힙 크기를 512MB로 설정합니다.Xmx4g
: JVM의 최대 힙 크기를 4GB로 설정합니다.XX:+UseConcMarkSweepGC
: CMS(Concurrent Mark-Sweep) 가비지 컬렉터를 사용합니다.XX:+CMSParallelRemarkEnabled
: CMS 가비지 컬렉션 중 병렬 리마크 단계를 활성화합니다.XX:+UseCMSInitiatingOccupancyOnly
: 힙 사용량 기반의 CMS 가비지 컬렉션 시작을 활성화합니다.XX:CMSInitiatingOccupancyFraction=70
: 힙의 70%가 사용되었을 때 CMS 가비지 컬렉션을 시작합니다.XX:+ScavengeBeforeFullGC
: 전체 GC 전에 젊은 세대의 가비지 컬렉션을 수행합니다.XX:+PrintGCDetails
,XX:+PrintGCDateStamps
: 가비지 컬렉션의 상세 정보와 타임스탬프를 콘솔에 출력합니다.Xloggc:gc-log.txt
: 가비지 컬렉션 로그를 'gc-log.txt' 파일에 기록합니다.
Java 버전별 디폴트 GC 알고리즘
- Java 1.3 이하
- Serial GC
- Java 1.4 ~ Java 5
- Parallel GC
- Java 6 ~ Java 8
- Parallel Old GC
- Java 8u40부터 G1 GC가 도입되었으나, Parallel Old GC가 여전히 디폴트다.
- Java 9 ~ Java 10
- G1 GC
- Java 11 이후
- G1 GC
- Java 11부터 ZGC가 실험적으로 도입되었으나 디폴트는 G1GC이다.
- Java 15부터 Shenandoah가 실험적으로 추가되었다.
Reference.
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
https://wiki.openjdk.org/display/zgc/Main#Main-JDK11
https://stackoverflow.com/questions/33206313/default-garbage-collector-for-java-8