[Java] JVM - Garbage Collector
0. 들어가기전에
GC는 여러 종류가 있고, 자바 버전별로 기본적으로 채택하는 GC가 다릅니다. JAVA 8에는 Parallel GC, JAVA 9이상 부터는 G1 GC가 사용이 됩니다. Parallel GC까지는 이전 GC와 동작방식이 비슷하지만, G1 GC는 동작하는 방법이 달라 부득이하게 내용이 나뉘게 되었습니다.
1. Garbage Collector
런타임에 실행 중인 프로그램에 대한 메모리 할당 및 회수를 관리하는 자동 메모리 관리 시스템
가비지 컬렉터는 가비지 컬렉션(Garbage Collection)이라는 과정을 통해 자바 어플리케이션에서 자동으로 메모리 관리를 해주는 시스템이다. 그렇다면 '가비지 컬렉션(이하 GC)를 자주 실행하는게 메모리 최적화도 되고 더 좋겠지?'라고 생각이 들 수 있다. 하지만, GC를 하면 stop-the-world
과정이 발생해 오히려 속도가 느려질 수 있다.
stop-the-world
란 GC가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드의 작업을 멈추게 하고, GC가 끝난 후 중단했던 작업을 다시 시작하는 과정이다. 이렇기 때문에 빈대를 잡으려다 초가삼간을 다 태우는 현상이 발생할 수도 있다. 따라서 GC의 대상과 원리를 이해하고 성능 튜닝을 하는게 중요하다.
2. GC의 대상
- 모든 객체 참조가 null인 경우
- 객체가 블록 안에서 생성되고 블록이 종료된 경우
- 부모 객체가 null이 된 경우, 자식 객체는 자동적으로 GC 대상이 된다.
- 객체가 Weak 참조만 가지고 있을 경우
- Weak 참조는 WeakReference 클래스로 구현
- 객체가 Soft 참조이지만 메모리 부족이 발생한 경우
- Soft 참조는 SoftReference 클래스로 구현
3. 자바 버전 별 Heap
3.1 JAVA 8 이전 Heap
JAVA 8 이전 Heap은 Young 영역, Old 영역, Perm 영역으로 구성되어 있다. Perm 영역에서는 클래스 메타 데이터, interned String, static 변수가 저장된다.
그렇다면 저번 JVM 글에서 읽었던 Method Area와 Perm 영역이 같지 않나? 라는 의문이 자연스럽게 떠오를 것이다. 이 글에 따르면 MethodArea는 논리적인 개념일 뿐 실제로는 구현이 안된다고 한다. 따라서 Heap 안의 Permanent 영역에서 static을 다룬다는 것은 MethodArea를 구현한 방법이라고 볼 수 있다.
3.2 JAVA 8 이후 Heap
공식문서에 따르면 JAVA 8 이후, Heap 영역의 Permanent 영역이 MetaSpace로 옮겨졌다고 한다. 하지만 기존 static Object를 관리하는 역할과 String Pool은 Heap에서 관리하도록 변경되었기 때문에, 참조가 해제된 static Object는 GC의 대상이 될 수 있다. MetaSpace는 Native Area에 위치해 JVM이 아닌 OS 레벨에서 관리 받는다. 따라서 제한된 크기를 가지고 있지 않고, 유동적으로 늘어날 수 있다.
4. GC 동작과정
4.1 Young 영역에서의 GC(Minor GC)
- 새로 생성된 객체는 Eden에 위치한다.
- GC가 발생 후 살아남은 객체는 Survivor 영역 중 하나로 이동한다.
- Eden에서 살아남은 객체가 계속 Survivor 영역으로 객체가 이동한다. (이 때, Survivor 영역 중 어느 한 곳은 반드시 비어 있어야 한다.)
- 위 과정을 반복하다가 계속해서 살아남은 객체를 Old 영역으로 이동시킨다.
'계속해서 살아남았다'의 기준은 각 객체가 age bit로 관리된다. Age bit는 minor GC가 발생할 때 마다 1씩 증가 되어 설정 값을 초과한 경우에 Old 영역으로 이동하게 된다. 추가로 Young 영역보다 큰 객체가 생성되면 Eden에서 생성되지 않고 Old 영역에서 생성된다.
아래는 위 과정을 그림으로 표현한 것이다.
4.2 Old 영역에서 GC(Major GC)
Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행한다. Major GC가 발생하면 stop-the-world
과정이 시작된다. Major GC 전략은 JAVA 7 기준으로 5개가 존재한다.
1. Serial GC
임베디드나 데이터가 100MB이하일 경우 효율적인 GC 방식이다. Young 영역에서의 GC는 앞에서 설명한 알고리즘을 사용하고, Old 영역에서는 mark-sweep-compact
알고리즘을 사용한다. mark-sweep-compact
알고리즘은 Old 영역에 살아 있는 객체를 식별(mark)하고 힙의 가장 앞부분 부터 살아있는 것만 남기고(sweep) 마지막으로 살아있는 객체들을 가장 앞쪽으로 모아준다.이 방식은 데이터가 큰 어플리케이션과 멀티 프로세서 하드웨어에서 효율적이지 않다.
2. Parallel/Throughput GC
Parallel GC는 다수의 쓰레드를 사용해 GC의 속도를 높인다. 기본적인 알고리즘은 Serial GC로 처리한다. Serial GC와 Parallel GC에서 스레드를 비교한 그림이다.
하지만 중단하는 시간(stop-the-world
)이 긴 단점이 있다.
3. Parallel Old GC
Parallel GC에서 Old 영역 GC 알고리즘을 mark-summary-compaction
으로 변경했다. mark-sweep-compact
의 sweep
보다 더 복잡하고 효율적인 단계라고 생각하면 된다.
4. CMS(Concurrent Mark Sweep) GC
중단하는 시간(stop-the-world
)이 적기 때문에 저지연이 필요한 애플리케이션에서 사용하기 좋다. 다른 GC 방식보다 CPU를 더 많이 사용하고 기본적으로 Compaction을 하지 않는다는 단점이 있다.
5. G1 GC
JAVA 8에서는 Parallel GC, JAVA 9부터는 G1 GC가 기본적으로 사용된다. CMS의 향상된 버전으로 CMS GC보다 효율적이고 동시에 Application과 GC를 진행할 수 있고, Compaction도 지원한다. 기존 힙 구조와 다르게 GC는 바둑판 모양으로 메모리로 나누고 region으로 관리한다.
※ 참조
'LANGUAGE > [JAVA]' 카테고리의 다른 글
[Java] EnumMap이란? (1) | 2023.03.24 |
---|---|
[Java] LinkedHashMap의 방어적 복사 (0) | 2023.03.13 |
[Java] JVM - 메모리 구조 (4) | 2023.02.28 |
[Java] 롬복의 원리 (0) | 2022.06.08 |
[JAVA] JVM (0) | 2022.06.04 |