가비지 컬렉션 정리
Naver D2의 글을 보고 정리한 글입니다.
사전 필요 지식:
- stop-the-world :
- GC 실행하기 위해 JVM이 애플리케이션을 멈추는 것
- ‘stop-the-world’발생후
GC 실행하는 스레드 외의 모든 스레드의 작업은 멈춘다.
GC튜닝
:- stop-the-world 시간을 줄이는 것
GC는 두가지 전제(가설;weak generational hypothesis)를 토대로 만들어 짐
- 대부분 객체는 금방 접근 불가능 상태가 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
Hot Spot VM에서는 크게
- Young영역(Young generation 영역)
- 새로 생성된 객체들이 있는 공간
- 대부분 객체는 곧
접금 불가능 상태
가 되기 때문에
Young영역에서 생성되고 사라진다. - 여기서 객체가 사라질 때
Minor GC
가 발생한다고 말한다.
- Old영역(Old generation 영역)
- Young영역에서
접근 불가능 상태
가 되지 않은 객체가 여기로 복사된다. - 대부분 Young영역보다 크게 할당함
- GC가 적게 발생
- 여기서 객체가 사라질 때
Major GC
orFull GC
가 발생한다고 말함
- Young영역에서
으로 나뉘었다.
기타 영역
- Permanent 영역(Permanent generation or Method area)
- 객체나 억류(?)된 문자열 정보를 저장하는 곳
- 여기서 GC가 발생하면
Major GC
횟수에 포함됨
Old 영역에 있는 객체가 Young 영역의 객체를 참조하고 있을 경우
- 카드 테이블
- Old영역에 512바이트의 덩어리로 된 카드 테이블이 존재
- Old영역에서 Young영역의 객체를 참조할 때마다 정보가 테이블에 표시됨
write barrier
를 사용하여 관리- Minor GC를 빠르게 할 수 있도록 하는 장치
- 약간의 오버헤드는 발생하나 전반적인 GC시간 감소
Young영역
Young영역의 구성
- Eden영역
- survivor영역(2개)
- 총 3개 영역
처리 절차
- 새로 생성한 대부분의 객체는 Eden영역에 위치함
- Eden 영역에서 GC가 한 번 발생한 후
살아남은 객체는 survivor영역 중 하나로 이동 - Eden영역에서 GC발생하면 계속 survivor영역에 객체가 쌓임.
- 하나의 survivor영역이 가득 차게 되면
그 중 살아남은 객체를 다른 survivor영역으로 이동
그리고 그 가득찬 survivor영역은 리셋됨. - 이 과정을 반복하며
계속 살아남아 있는 객체는 Old영역으로 이동- survivor영역 중 한 곳은 반드시
빈
상태로 있어야 함. - 만약 servivor두 영역에 모두 데이터가 있거나
모두 사용량이 0이면
그 시스템은 정상적인 상황이 아닌것
- survivor영역 중 한 곳은 반드시
참고
HotSpot VM은 빠른 메모리 할당을 위해 아래의 기술을 사용
- bump-the-pointer
- Eden영역에 할당된 마지막 객체(맨 위에 있는)를 추적
- 생성되는 객체의 크기가 Eden영역에 넣기에 적당한지 확인
- 만약 적당하다면 Eden영역에 넣고 그 객체가 마지막 객체가 됨(맨 위에 있게 됨)
- 새로 추가된 객체만 검사하면 되므로 메모리 할당이 빠름
- bump-the-pointer이 멀티스레드 환경에서 lock이 발생하는 문제가 있음
- 이를 보완하기 위해 이 TLABs필요.
- TLABs(Thread-Local Allocation Buffers)
- 각각 스레드가 각자의 몫에 해당하는 Eden영역의 작은 부분을 가질수 있도록 함
- 각 스레드는 자기 몫의 TLABs만 접근할 수 있서
bump-the-pointer 기술 사용해도 아무런 락 없이 메모리 할당 가능
중요 포인트
- Eden영역에 최초로 객체가 만들어지고
servivor영역을 통해 Old영역으로 오래 살아남은 객체가 이동한다
Old영역
Old영역은 기본적으로 데이터가 가득차면 GC 실행.
- 기본적인 GC 방식 5가지(jdk 7 기준)
- Serial GC
- Parallel GC
- Parallel Old GC(Parallel Compacting GC)
- Concurrent Mark & Sweep GC(CMS)
- G1 GC(Garbage First GC)
이 중 운영 서버에서 절대
사용하면 안되는 방식은 Serial GC
다.
- Serial GC는 CPU 코어가 하나만 있을 때 사용하기 위해 만든 방식
- Serial GC는 애플리케이션의 성능을 떨어뜨림
- 각 GC의 대략적인 절차
- 그림 출처 : https://www.dhaval-shah.com/images/wp-content/uploads/2019/11/types-of-gc.png __
Serial GC (-XX:+UseSerialGC)
- Young영역에서의 GC는 위의 설명한 방식과 같음(처리 절차)
- Old영역의 GC는
mark-sweep-compact
라는 알고리즘을 사용- Old영역에 살아 있는 객체 실별(mark)
- 힙(heap)의 앞 부분부터 확인하여 살아 있는 것만 남김(sweep)
- 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채움
- 객체가 있는 부분과 없는 부분으로 나뉨(compact)
- 적은 메모리, cpu 코어 갯수가 적을 때 적합
Parallel GC (-XX:+UseParallelGC)
- 기본적인 알고리즘은 Serial GC와 같음
그러나 Serail GC는 스레드가 하나이고 Parallel GC는 여러개 - 그래서 Serial GC보다 빠르게 처리
- 많은 메모리, cpu 코어 갯수가 많을 때 적합
- Throughput GC라고도 함
Parallel Old GC(-XX:+UseParallelOldGC)
- Parallel GC방식의 Old영역 GC 알고리즘만 다름
- Mark-Summary-Compaction 단계를 거침.
- summary 단계는 앞서 GC를 수행한 영역에 대해
별도로 살아 있는 객체를 식별 - mark-sweep-compaction알고리즘의 sweep단계와 다르고
더 복잡한 단계를 거침
- summary 단계는 앞서 GC를 수행한 영역에 대해
Deprecated in Java 9 and Removed Java 14 CMS GC (-XX:+UseConcMarkSweepGC)
- 초기 Initial Mark 단계
- 클래스 로더에서 가장 가까운 객체 중
살아 있는 객체 찾음
- 클래스 로더에서 가장 가까운 객체 중
- Concurrent Mark 단계에서는
Initial Mark에서 살아 있는 객체들을 따라가면서 확인 - 다른 스레드가 실행 중인 상태에서 동시에 진행
- remark 단계
- Concurrent Mark 단계에서
새로 추가되거나 참조가 끊긴 객체를 확인
- Concurrent Mark 단계에서
- Concurrent sweep단계
- 쓰레기를 정리하는 작업 실행
(다른 스레드가 실행되고 있는 상황에서)
- 쓰레기를 정리하는 작업 실행
- Low Latency GC라고도 부름
- 응답속도가 중요할 때 사용
- 장점
- stop-the-world 시간이 짧음
- 단점
- 다른 GC보다 메모리, CPU를 많이 사용
- Compaction단계가 기본적으로 제공되지 않음
- 사용 전 신중하게 검토후 사용 필요
- 조각난 메모리가 많아 Compaction작업을 실행하면
다른 GC방식의 stop-the-world 시간보다 더 길기에
Compaction 작업이 얼마나 자주, 오랫동안 수행하는지 확인!!
- G1 GC시간
- 그림 출처 : https://docs.oracle.com/javase/9/gctuning/img/jsgct_dt_004grbg_frst_hp.png __ G1 GC
- 바둑판 영역에 객채를 할당 및 GC 실행
- 해당영역이 꽉 차면 다른 영역에 객체를 할당하고 GC실행
- 위의 Young->Old영역 하는 절차가 빠짐
- CMS GC를 대체하기 위해 만들어짐
- 성능이 제일 좋음
Leave a comment