가비지 컬렉션(GC)
JVM에서 더 이상 필요없는 객체를 찾아 지우는 작업
관련 용여
stop-the-world: GC 실행을 위해 JVM이 애플리케이션 실행을 멈추는 것으로, GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춤
GC 튜닝: stop-the-word 시간을 줄이는 것
가비지 컬렉터 설계시 가정된 것: weak generational hypothesis
1. 대부분의 객체는 금방 접근 불가 상태(unreachable)가 됨
2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재
Young 영역과 Old 영역
weak generational hypothesis(약한 세대 가설)의 장점을 최대한 살리기 위해 HotSpot VM에서 물리적 공간을 두 가지로 나는 방식
Young 영역
- 새롭게 생성한 객체의 대부분이 위치
- 대부분의 객체가 생성되었다가 사라지는 영역(대부분의 객체가 금방 접근 불가 상태가 되므로)
- 이 영역의 객체가 사라질때 Minor GC가 발생한다고 말함
- 3개의 영역으로 구성(Eden 영역, 두 개의 Survivor 영역)
- 각 영역의 처리 절차
▶ 새로 생성한 대부분의 객체는 Eden 영역에 위치
▶ Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동
▶ Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓임
▶ 하나의 Survivor 영역이 가득 차면 그 중 살아남은 객체를 다른 Survivor 영역으로 옮기고, 가득 찬 영역은 비움
▶ 이 과정을 반복하여 살아남은 객체를 Old 영역으로 이동
Old 영역
- Young 영역에서 살아남은 객체가 복사되는 위치
- 대부분 Young 영역보다 크게 할당
- Young 영역보다 GC가 절게 발생
- 이 영역의 객체가 사라질때 Major(Full) GC가 발생했다고 말함
- 기본적으로 데이터가 가득 차면 GC를 실행
- Old 영역의 GC 방식 5가지(JDK 7 기준)
▶ Serial GC
▶ Parallel GC
▶ Parallel Old GC
▶ Concurrent Mark & Sweep GC (CMS)
▶ G1(Garbage First) GC
Serial GC
- CPU 코어가 하나인 데스크톱에 사용하기 위해 만든 방식
- 사용하면 애플리케이션 성능이 떨어지므로 운영 서버에서 사용 금지
- Young 영역의 GC는 위에서 설명한 방식, Old 영역의 GC는 mark-sweep-compact 알고리즘 사용
- mark-sweep-compact 알고리즘 작동 방식
1. Old 영역의 살아 있는 객체 식별(Mark)
2. 힙의 앞 부분부터 확인하여 살아 있는 것을 남김(Sweep)
3. 각 객체들이 연속되게 쌓이도록 힙의 가자 앞 부분부터 채워서 객체가 존재하는 부분과 없는 부분으로 나눔(Compaction)
Parallel GC(== Throughput GC)
- Serial GC와 기본적인 알고리즘이 동일함
- GC를 처리하는 쓰레드가 여러 개이므로 Serial GC보다 빠르게 객체를 처리 (<-> serial GC는 처리하는 스레드가 하나)
- 메모리가 충분하고 코어의 개수가 많을 떄 유리
- 자바 8 이전 버전의 default
Parallel Old GC
- Parallel GC와 다르게 Old 영역의 GC 알고리즘에서 Mark-Summary-Compaction 단계를 거침
- Summary 단계는 앞서 GC를 수행한 영역에 대해 별도로 살아 있는 객체를 식별한다는 점에서 Mark-Sweep-Compaction 알고리즘의 Sweep 단계와 다르며, 더 복잡한 단계를 거침
- JDK 5 update 6부터 제공
CMS GC
- 작동 방식 (이전에 설명한 GC 방식보다 복잡함)
1. Initial Mark 단계: 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 종료(멈추는 시간이 매우 짧음)
2. Concurrent Mark 단계: 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인 (다른 스레드가 실행 중인 상태에서 동시에 진행)
3. Remark 단계: Cuncurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인
4. Concurrent Sweep 단계: 쓰레기를 정리하는 작업 실행(다른 스레드가 실행 중인 상태에서 동시에 진행)
- stop-the-world 시간이 매우 짧으므로, 애플리케이션의 응답 속도가 매우 중요할 때 사용하며, Low Laytency GC라고 부름
- 단점: 1. 다른 GC 방식 보다 메모리와 CPU 사용량↑ 2. Compaction 단계가 기본적으로 제공 x
(조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 보다 stop-the world 시간이 더 긺)
G1(Garbage First) GC
- 작동 방식
1. 바둑판 각 영역에 객체를 할당하고 GC를 실행
2. 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행
- Young 영역에서 Old 영역으로 데이터가 이동하는 단계가 사라짐
- 장점: 우수한 성능
- JDK 7 정식 포함 (JDK 6에서 시험 사용)
- 자바 9 이후 버전의 default
영역별 데이터 흐름
Permanent Generation 영역 (== Method Area)
- 객체나 억류(intern,각각의 고유 한 문자열 값의 사본 하나만 저장하는 방법)된 문자열 정보를 저장
- 해당 영역에서 GC가 발생할 수도 있으며(객체가 영원히 남는 공간이 아님), 이는 Major GC 횟수에 포함
Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우 어떻게 처리될까?
Old 영역에 이를 위한 512바이트 덩어리의 카드 테이블이 존재
카드테이블
- Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시
- Young 영역 GC를 실행할 때 Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 카드 테이블만 뒤져서 GC대상인지 식별
- write barrier를 사용하여 관리(Minor GC 가속 장치), 이로 인해 약간의 오버헤드가 발생하지만 GC 시간 감소
HotSpot VM에서 빠른 메모리 할당을 위해 사용하는 기술
1. bump-the-pointer
- Eden 영역에 할당된 마지막 객체를 추적한다. 마지막 객체는 Eden 영역의 맨 위(top)에 있다. 그리고 그 다음에 생성되는 객체가 있으면, 해당 객체의 크기가 Eden 영역에 넣기 적당한지만 확인한다. 만약 해당 객체의 크기가 적당하다고 판정되면 Eden 영역에 넣게 되고, 새로 생성된 객체가 맨 위에 있게 된다. 따라서, 새로운 객체를 생성할 때 마지막에 추가된 객체만 점검하면 되므로 매우 빠르게 메모리 할당이 이루어진다.
2. TLABs(Thread-Local Allocation Buffers)
- 멀티 스레드 환경을 고려하면 이야기가 달라진다. Thread-Safe하기 위해서 만약 여러 스레드에서 사용하는 객체를 Eden 영역에 저장하려면 락(lock)이 발생할 수 밖에 없고, lock-contention 때문에 성능은 매우 떨어지게 될 것이다. HotSpot VM에서 이를 해결한 것이 TLABs이다. 각각의 스레드가 각각의 몫에 해당하는 Eden 영역의 작은 덩어리를 가질 수 있도록 하는 것이다. 각 쓰레드에는 자기가 갖고 있는 TLAB에만 접근할 수 있기 때문에, bump-the-pointer라는 기술을 사용하더라도 아무런 락이 없이 메모리 할당이 가능하다.
'자바' 카테고리의 다른 글
[Java] 자바의 연산자 (0) | 2021.08.20 |
---|---|
[Java] 자바 데이터 타입(자료형), 변수 그리고 배열 (0) | 2021.08.18 |
[JAVA] 객체지향 - 다형성(Polymorphism) (2) | 2021.08.13 |
[Java] 자바 상속의 특징 - extends, super, 오버라이딩, instanceof, 추상 클래스와 메소드, final (0) | 2021.08.12 |
[Java] 접근 제한자(제어자) - public, protected, default, private (0) | 2021.08.12 |
댓글