🎗 Garbage Collection이란?
자바의 메모리 관리 방법 중 하나이다. JVM의 Heap 영역에서 동적으로 할단했던 메모리 중 필요가 없게 된 메모리 객체를 모아 주기적으로 제거하는 프로세스를 말하게 된다
Java는 가비지 컬렉터가 메모리 관리를 대행해주고 있기 때문에 Java 프로세스가 한정된 메모리를 효율적으로 사용할 수 있게 하고, 개발자 입장에서 메모리 관리, 메모리 누수 문제에서 관리하지 않고 오롯이 개발에 집중을 할 수 있다는 것이다
그렇다면 가비지 컬렉터의 단점은 무엇일까?
물론 메모리를 자동으로 제어해준다는 것은 매우 매력적인 내용이다. 하지만 이가 무조건적인 장점이라고 볼 수 는 없다는 것이다.
- 메모리가 언제 해제되는지 정확하게 알 수가 없기 때문에 제어가 힘들다
- 가비지 컬렉션이 작동하면 다른 동작을 멈춘다 다른 동작을 멈춘다는 것은 소프트웨어의 성능 하락과도 크게 연관이 되기도 한다.
이로 인해 가비지 콜렉션의 최적화 작업이 개발자의 숙제가 되는 것이다.
♬ GC의 청소 방식, Mark And Sweep ( 기초적인 청소 과정 )
그렇다면 현재 메모리에 필요가 없게된 메모리의 객체를 어떻게 청소하는 것인지 알아보자
원리
GC가 될 대상 객체를 식별(Mark)하고 제거(Sweep)하며 객체가 제거되어 파편화된 메모리 영역을 앞에서 부터 채워져 나간다
- Mark : GC의 Root Space로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다
- Sweep : 참조하고 있지 않은 객체들을 Heap에서 제거한다
- compact : 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축한다.
Root Space
Mark And Sweep 방식은 루트로 부터 해당 객체에 접근이 가능한지가 해제의 기준이 된다
Root Space는 Heap 메모리 영역을 참조하는 method area, static 변수, stack이 된다
♬ Garbage Collection의 동작 과정
힙 영역은 동적으로 레퍼런스 데이터가 저장되는 공간이다. 또한 위에서 계속 말했듯이 GC의 대상이 되는 공간이다
힙 영역은 2가지를 전제로 설계되는데
- 대부분의 객체는 금방 접근 불가능한 상태가 된다
- 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존대한다.
객체는 대부분 일회성이다. 메모리에 굉장히 오랫동안 남아있는 경우가 거의 없다는 것이다
이렇게 쉽게 사용이 끊기는 Heap영역의 객체를 위해 메모리 관리를 위해 Heap영역을 Young과 Old로 나누었다
Young 영역
- 새롭게 생성된 객체가 할당되는 곳
- 대부분 객체가 금방 끊기기때문에 많은 객체가 들어오고 나간다
- Yuong 영역을 Minor GC라고 부른다
Old 영역
- 오랫동안 살아남은 객체가 있는 곳
- Young보다 넓은 크기로 할당되고 Garbage는 적게 발생한다
- 해당 영역을 Major GC라고 부른다
Young 영역의 세분화
Eden
- new를 통해 새로 생성된 객체가 위치한다.
- 정기적인 쓰레기 수집 후 살아남은 객체들을 Survivor 영역으로 보낸다
Survivor 0 / Survivor 1
- 최소 1번의 GC이상 살아남은 객체가 존재하는 영역
- Survivor 영역에는 특별한 규칙이 있는데, Survivor 0 또는 Survivor 1 둘 중 하나에는 꼭 비어 있어야 한다는 것이다.
🎗 Minor GC 와 Major GC
♬ Minor GC의 처리 과정
Young 영역은 짧게 살아남는 메모리들이 존재하는 공간이다 처음으로 생성된 모든 객체는 여기에 생성된다
Young 구역은 상대적으로 작기때문에 메모리 상의 객체를 찾아서 제거하는데 적은 시간이 걸린다
선행학습 Age
Survivor 영역에서 객체의 객체가 살아남은 횟수를 의미한다. Header에 기록된다
age의 임계값(31)에 다다르면 Old 영역으로 갈것인가에 대한 여부를 결정한다
Survivor 영역의 제한 조건으로 Survivor 영역 중 반드시 1개는 사용되어야 한다. 또 한개가 사용되면 한개는 무조건 비어져있어야 한다
- 처음 생성된 객체가 Young의 Eden으로 들어간다
- Eden영역이 꽉 차게 되면 이때 MinorGC가 실행된다
- Mark를 통해 현재 참조가 유지되고 있는 객체를 선택하고 Survivor 0 구역으로 이동한다
- 참조가 해제된 객체는 메모리에서 해제시킨다(sweep)
- 한 번 살아남은 객체는 age값이 1이 증가한다
- 또 다시 여러번의 Eden의 영역에 새로운 객체가 꽉차면 위의 과정을 반복한다. 하지만 이때 Survivor 0의 영역에 있는 객체도 Mark 체크를 해야한다
- 이때 0에 한 번 데이터를 넣었기 때문에 이번엔 Survivor 1 영역에 Mark된 객체를 넣어준다. 물론 Survivor 0 영역의 객체도 넘겨주어야 한다
- Eden 공간과 Survivor 0에 참조가 끊긴 값을 지우고 Survivor 1로 넘어간 객체는 age를 1을 더 추가해주어야 한다
- 이 후 과정은 무한적으로 반복한다
♬ Major GC의 처리 과정
Old 영역은 길게 살아남은 메모리들이 존재하는 공간이다
위에서 age에 대한 설명에 남겼듯이 age의 임계값이 차게 되면서 이동된 객체들인 것이다
그리고 Major GC는 객체들이 계속 Promotion되어 Old영역의 메모리가 부족해지면 발생한다.
Major GC의 실행 시점은 Old 영역이 꽉차면 실행된다.
- 객체의 age가 임계값에 도달하면 넘어간다 이 과정을 Promotion이라고 한다
- Promotion의 과정이 쌓여서 Old의 공간에도 꽉 찬다면 Major GC가 시작된다.
Minor에 비해 Major는 단순한 방식으로 실행된다
Old영역에 있는 모든 객체들을 검사해서 참조되지 않는 객체들을 한꺼번에 삭제하는 Major GC가 실행되게 된다는 것이다.
하지만 공간이 크기 때문에 객체 제거에 많은 시간이 걸린다
Major GC는 Minor에 비해 무려 10배 이상의 시간을 더 많이 사용하기도 한다. 그러면 이때 GC를 하는 동안 다른 동작이 멈추게 되고 Stop the world 문제가 발생한다는 것이다.
Stop The World
GC를 수행하기 위해 JVM이 프로그램 실행을 멈추는 현상을 의미한다
GC가 작동하는 동안 GC 관련 Thread를 제외한 모든 Thread는 멈추게 되고 서비스 이용에 차질이 생길수 있다는 것이다
그래서 개발자는 이를 더욱 빠르게 진행시키기 위해 Major GC의 알고리즘을 발전시켰다
🎗 Garbage Collection의 알고리즘 종류
1. Serial GC
- 서버의 CPU 코어가 1개일때 사용하기 위해 개발된 가장 단순한 GC
- GC를 처리하는 쓰레드가 1개라서 STW가 가장 길다
- Minor GC에는 Mark-Sweep을 사용하고, Major GC에는 Mark-Sweep-Compact를 사용한다.
2. Parallel GC
- Java 8의 디폴트 GC
- Serial GC와 기본적인 알고리즘은 같지만, Young 영역의 Minor GC를 멀티 쓰레드로 수행한다
하지만 Old 는 싱글 스레드 - Serial GC에 비해 STW가 감소한다
3. Parallel Old GC(Parallel Compacting Collector)
- Parallel GC를 개선한 버전
- Young 영역 뿐만이 아니라, Old 영역에서도 멀티 쓰레드로 GC를 수행한다
- 새로운 가비지 컬렉션 철소 방식인 Mark-Summary-Compact 방식을 이용한다.
4. CMS GC(Concurrent Mark Sweep)
- 어플리케이션의 쓰레드와 GC쓰레드가 동시에 실행되어 STW시간을 최대한 줄이기 위해 고안되었다
- GC과정이 매우 복잡해진다
- GC대상을 파악하는 과정이 복잡한 여러 단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높다
- 메모리 파편화의 문제가 발생한다
- 하지만 너무 복잡해지면서 deprecated되었다
4. G1 GC(Garbage First)
- CMS GC를 대체하기 위해 고안된 GC방식이다
- Java 9+버전에서 디폴트로 지정되었다
- 4GB 이상의 힙메모리, STW시간이 0.5초 정도 필요한 상황에서 사용한다
- 기존의 GC 알고리즘에서는 Heap 영역을 물리적으로 고정된 Young/Old 영역으로 나누어 사용하였지만, G1은 Region을 도입해서 사용한다
- 전체 Heap 영역을 Region이라는 영역으로 체스같이 분할해서 상황에 따라 Eden, Survivor, Old등 역할을 고정이 아닌 동적으로 부여하기 시작했다
- Garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로, 결국 GC 빈도가 줄어드는 효과를 얻게 되는 원리가 되는 것이다.
G1 의 효율성
이전의 GC처럼 일일히 메모리를 탐색해서 객체들을 제거하는 것이 아니다.
대신 메모리가 많이 차있는 영역을 인식하고 우선적으로 GC를 한다
즉, G1은 Heap Memory 전체를 탐색하는 것이 아니라 영역별로 나누고 탐색하고 GC를 발생시킨다
또한 기존의 GC 방식은 Eden - Survivor 0 - Survivor 1 였지만 여긴 순차적으로 이동하는 것이 아니라 G1이 더욱 효율적이하고 생각하는 위치로 객체를 재할당 시킨다.
예를 들어 Survivor 1 영역에 있는 객체더라도 Eden 영역에 가는 것이 더욱 효율적이라고 판단 된다면 Eden으로 옮긴다는 것이다.
G1 GC의 과정
Minor GC
- 연속되지 않은 메모리 공간에 Young 객체들이 Region단위로 메모리에 할당 (STW 발생)
- 참조가 계속되고 있는 객체를 Survivor Region으로 이동
Major GC
- Initial Mark : Old Region에 존재하는 객체들이 참조하는 Survivor Region을 찾는다 (STW 발생)
- Root Region Scan : Inition Mark 에서 찾은 Survivor Region에 대한 GC 대상 객체 스캔 작업을 진행한다
- Concurrent Mark : 전체 힙의 Region에 대해 스캔 작업을 진행하며, GC 대상 객체가 발견되지 않은 Region은 이후 단계를 처리하는데 제외되도록 한다.
- Remark : 어플리케이션을 멈추고(STW) 최종적으로 GC 대상에서 제외될 객체를 식별한다
- CleanUp : 어플리케이션을 멈추고(STW) 살아있는 객체가 가장 적은 Region에 대한 미사용 객체 제거를 수행한다. 이후 앞선 GC과정에서 완전히 비워진 Region을 Freelist에 추가하여 재사용될 수 있도록 한다
- Copy : GC대상 Region이었지만 CleanUp 과정에서 완전히 비워지지 않은 Region의 살아남은 객체들을 새로운 Region에 복사해서 Compaction 작업을 수행한다.
🎗 참조
'CS > 📝 언어' 카테고리의 다른 글
🖨 new String() 과 ""의 차이 (0) | 2024.06.04 |
---|---|
🖨 절차 지향과 객체 지향 프로그래밍의 차이 (0) | 2024.05.22 |
🖨 OOP란?? (0) | 2024.05.22 |
추상 클래스와 인터페이스의 차이 (0) | 2023.10.26 |
Java의 컴파일 과정 & JVM의 구조 (0) | 2023.10.18 |