Garbage Collection 정리
in Programming on Java
GC (Garbage Collection)이 메모리를 정리하는 과정을 알아보자.
Stop-the-world
GC를 실행하기 위해 JVM이 어플리케이션 실행을 멈추는것. stop-the-world가 발생하면 GC를 실행하는 thread를 제외한 나머지 thread는 모두 작업을 멈춤. GC 작업 이후 중단했던 작업을 다시 시작. GC튜닝은 보통 stop-the-world 시간을 줄이는것을 의미.
Garbage Collector
Java에서는 개발자가 프로그램 코드로 메모리를 명시적으로 해제하지 않기때문에 GC가 더이상 필요없는 객체를 찾아 지우는 작업을 한다. GC는 weak generational hypothesis 라는 2가지 가설 하에 만들어졌다.
weak generational hypothesis
- 대부분의 객체는 금방 접근 불가능상태가 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존제한다.
JVM(HotSpot VM)의 heap 메모리영역
이러한 가설의 장점을 살리기 위해 JVM 메모리(그중에서도 heap 영역)는 아래와 같이 영역이 나뉜다.
Generation | Description |
---|---|
Young 영역 | 새롭게 생성한 객체의 대부분이 여기에 위치. 대부분의 객체가 금방 접근 불가 상태가 되므로 많은 객체가 Young 영역에 생겼다가 사라진다. Young 영역에서 객체가 사라지는것을 Minor GC가 발생한다고 한다. |
Old 영역 | 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young영역보다 크게 할당하며 크기가 큰 만큼 Young 영역보다는 적게 GC가 발생한다. Old 영역에서 객체가 사라지는것을 Major GC(Full GC)가 발생한다고 한다. |
Permanent 영역 | Method Area. 객체나 억류된 문자열 정보를 저장. Old 영역에서 살아남은 객체가 영원히 남아있는곳은 아님. 이곳에서도 GC 가 발생. Major GC 횟수에 포함됨. |
Old 영역의 객체가 Young 영역의 객체를 참고하면?
Old 영역에는 Young 영역 객체 참조정보를 저장하는 Card Table이 존재. 따라서 Young 영역에 GC를 실행할 때, Old 영역의 모든 객체의 참조정보를 뒤지는게 아니라 Card Table만 뒤져서 GC 대상인지 여부를 판별.
Young 영역의 구성과 GC
Young 영역은 총 3개 영역으로 나뉜다.
- Eden 영역
- Survivor 영역 1
- Survivor 영역 2
각 영역의 처리 절차는 다음과 같다.
- 새로 생성한 객체는 Eden 영역에 위치
- Eden 영역에서 GC가 발생한 후 살아남은 객체는 Survivor 영억 중 한곳으로 이동
- Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역에 객체가 계속 쌓임
- 한 Survivor 영역이 가득 차면 그 중 살아남은 객체를 다른 Survivor 영역으로 이동. 가득찬 Survivor 영역은 clear됨.
- 이 과정을 반복하다가 계속 살아남는 객체는 Old 영역으로 이동.
즉 Survivor 영역 중 하나는 반드시 비어있는 상태로 남아있어야한다.
만약 두 Survivor 영역 모두에 데이터가 존재하거나 두 영역 모두 사용량이 0이면 비정상적인 상황이다.
JVM(HotSpot VM)에서 빠른 메모리할당을 위해 사용하는 기술
bump-the-pointer
Eden영역에 할당된 마지막 객체 추적. 마지막 객체는 Eden 영역의 맨 위에 있다. 그 다음에 생성되는 객체가 있으면 해당 객체의 크기가 Eden 영역에 넣기 적당한지만 확인한다. 만약 해당 객체의 크기가 적당하다고 판단되면 Eden영역에 넣고 새로 생성된 객체가 맨 위에 들어간다. 따라서 새로운 객체를 생성할 때 마지막에 추가된 객체만 점검하면 되므로 매우 빠르게 메모리할당이 이루어진다.
TLABs(Thread-Local Allocation Buffers)
multi-thread를 사용하는 객체를 thread-safe하게 Eden영역에 저장하려면 lock이 발생할수밖에 없고 이로인해 성능이 떨어질수밖에없다. 이를 해결하기 위한것이 TLABs 방법이다.
각각의 thread가 각각의 몫에 해당하는 Eden 영역의 작은 덩어리를 가질 수 있도록 하는것이다. 각 thread에는 자기가 갖고있는 TLAB만 접근할 수 있기 때문에 bump-the-pointer 기술을 사용해도 락 없이 메모리 할당이 가능하다.
Old 영역에 대한 GC
Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행한다. GC 방식에 따라 처리 절차가 달라진다.
Serial GC
싱글 스레드로 Young 영역, Old 영역을 처리. Young 영역은 앞에서 설명한 방법으로, Old 영역은 mark-sweep-compact 알고리즘을 사용해 메모리를 정리한다. 이 알고리즘의 첫단계는 Old 영역에 살아있는 객체를 식별(Mark) 하고 heap의 앞단부터 확인하여 살아있는 것만남긴다.(Sweep) 마지막으로 객체들이 연속되게 쌓이도록 heap의 가장 앞부분부터 채워서 객체가 존재하는 부분과 존재하지않는 부분으로 나눈다. (Compaction) 이는 CPU 코어가 하나일때 사용하기 위한 방식으로 이 방식을 사용하면 성능이 많이 떨어지므로 운영서버에서 사용하면 안되는 방식이다.
Parallel GC
Minor GC를 여러개의 스레드를 이용하여 동시에 처리. Major GC는 싱글 스레드로 처리. Serial GC와 기본적인 골잔즌 같지만 GC 를 처리하는 thread가 여러개라는 차이점이 있다. 때문에 serial GC 보다는 빠르다. Parallel GC는 메모리가 충분하고 코어의 갯수가 많을때 유리하다.
Parallel Old GC
Minor GC, Major GC를 모두 여러개의 스레드를 이용하여 동시에 처리. Parallel GC와 Old 영역의 GC 알고리즘만 다르다. 이 방식은 mark-summary-campaction 단계를 거친다.
Concurrent Mark & Sweep GC (CMS GC)
CMS GC는 타 GC 보다 더 복잡하다.
절차
(1) Initial Mark : class loader에서 가장 가까운 객체 중 살아있는 객체만 찾고 끝난다.
(2) Cuncurrent Mark : 방금 살아있다고 확인한 객체가 참조하는 객체를 따라가면서 확인한다. (다른 thread와 동시진행)
(3) Remark : Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다.
(4) Cuncorrent Sweep : 쓰레기를 정리하는 작업을 실행한다. (다른 thread와 동시진행)
장점
이러한 단계로 진행되는 GC 방식이기 때문에 stop-the-world 시간이 매우 짧다. 어플리케이션 응답속도가 매우 중요할때 CMS GC를 사용하며 Low Latency GC 라고도 부른다.
단점
- 다른 GC 방식보다 CPU와 메모리를 더 많이 사용한다.
- Compaction 단계가 기본적으로 제공되지 않는다.
G1 (Gargbage First) GC
메모리를 블록단위의 region으로 나누어서 데이터를 할당. Young, Old 영역이 없음. 블록을 사용 목적으로 구분하여 데이터를 할당하고, 해당 블록만 GC를 실행.
Young 영역에서 Old 영역으로 데이터가 이동하는 단계가 사라진 GC 방식. 이 방식의 가장 큰 장점은 성능. 다른 모든종류의 GC보다 빠르다.
참고 https://118k.tistory.com/843