Naver D2에 워낙 잘 설명돼있어서 이 페이지만 읽어봐도 GC에 대해 개념을 잡을 수 있다. 이 포스팅은 그냥 내가 읽기 편하려고 Naver D2 포스팅을 요약 정리한 것이다. (마찬가지로 D2에서 올린 Garbage Collection 튜닝도 매우 유익하다.)
Java GC(Garbage Collection)
Java에서는 개발자가 코드로 메모리를 명시적으로 해제하지 않기 때문에 Garbage Collector가 더는 필요 없는 객체를 찾아 지우는 작업을 한다. Garbage Collector는 'weak generational hypothesis'라는 두 가지 전제 조건에 의해 만들어졌다.
- 대부분 객체는 금방 접근 불가능 상태(unreachable)가 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
이 가설의 장점을 최대한 살리기 위해 Young 영역, Old 영역, Perm 영역으로 물리적 공간을 나누었다.
- Young 영역(Young Generation 영역): 새롭게 생성한 객체 대부분이 여기에 위치한다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 많은 객체가 Young 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질 때 Minor GC가 발생한다고 표현한다.
- Old 영역(Old Generation 영역): 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(또는 Full GC)가 발생했다고 말한다.
- Perm 영역(Permanent Generation 영역): Method Area(Java 메모리 구조참고)라고도 한다. 객체나 억류(intern)된 문자열 정보를 저장하는 곳이며, Old 영역에서 살아남은 객체가 영원히 남아 있는 곳은 절대 아니다. 이 영역에서 GC가 발생하기도 하는데, 여기서 발생한 GC도 Major GC에 포함된다.
stop-the-world
GC를 공부하기 전에 꼭 알아야 하는 용어이다. stop-the-world는 GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다. stop-the-world가 발생하면 GC를 실행하는 Thread를 제외한 나머지 Thread는 모두 작업을 멈춘다. 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생하며 이 시간은 성능에 큰 영향을 미친다.
Young 영역
Young 영역은 3개의 영역으로 나뉜다.
- Eden 영역
- 2개의 Survivor 영역
새로 생성한 대부분의 객체는 Eden 영역에 위치한다. Minor GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다. 다음 Minor GC에서도 같은 Survivor 영역에 살아남은 객체들이 쌓이게 된다. 그러다가 하나의 Survivor 영역이 가득 차면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득찬 Survivor 영역은 아무 데이터도 없는 상태로 비워진다. 이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게 된다.
Old 영역
Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행한다. GC 방식에 따라서 처리 절차가 달라지므로, 알맞은 GC 방식을 개발한 시스템에 적용해야 한다.
Serial GC
-XX:+UseSerialGC
- Young 영역과 Old 영역이 Serial하게(연속적으로) 처리되며 하나의 CPU를 사용한다.
- Serial GC는 적은 메모리와 CPU 코어 개수가 적을 때 적합한 방식이다.
- 일반적으로 클라이언트 종류의 장비에서 사용한다. 즉, 대기 시간이 많아도 크게 문제가 되지 않는 시스템에서 사용한다.
- 운영 서버에서 절대 사용하면 안 되는 방식이다. 데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식으로 이것을 사용하면 애플리케이션의 성능이 많이 떨어진다.
Young 영역에서의 GC는 앞 절에서 설명한 방식을 사용한다. Old 영역의 GC는 Mark-Sweep-Compaction 알고리즘을 사용한다.
Mark-Sweep-Compaction
쓰는 객체만 표시해두고 한 곳으로 모은다.
- Old 영역에서 살아 있는 객체를 식별한다. - Mark
- Heap의 앞 부분부터 확인하여 살아 있는 것만 남긴다. - Sweep
- 각 객체들이 연속되게 쌓이도록 Heap의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다. - Compaction
Parallel GC
-XX:+UseParallelGC
- 다른 CPU가 대기 상태로 남아 있는 것을 최소화 하는 것이 목표다.
- 메모리가 충분하고 코어의 개수가 많을 때 유리하다.
Throughput GC라고도 부른다. Serial GC는 GC를 처리하는 스레드가 하나인 것에 비해, Parallel GC는 GC를 처리하는 쓰레드가 여러 개이다. Young 영역에서의 Collection을 병렬(Parallel)로 처리한다. Old 영역은 마찬가지로 Mark-Sweep-Compaction 알고리즘을 사용한다.
Parallel Old GC(Parallel Compacting GC)
-XX:+UseParallelOldGC
Parallel GC와 비교하여 Old 영역의 GC 알고리즘만 다르다. Mark-Summary-Compaction 단계를 거친다. 결국 Sweep과 Summary의 차이다. Sweep은 단일 Thread가 Old 영역 전체를 훑어 살아있는 객체만 찾아내는 방식이지만, Summary는 여러 Thrad가 Old 영역을 분리하여 훑는다. 그리고 효율을 위해서 앞선 GC에서 Compaction 된 영역을 별도로 훑는다.
Concurrent Mark & Sweep GC (CMS)
-XX:+UseConcMarkSweepGC
- Heap 메모리 영역의 크기가 클 때 적합하다.
- (장점) stop-the-world 시간이 매우 짧다.
- 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 부른다.
- (단점) 다른 GC 방식보다 메모리와 CPU를 더 많이 사용한다.
- (단점) Compaction 단계가 기본적으로 제공되지 않는다.
Young 영역의 GC는 Parallel GC와 동일하다. Old 영역의 GC는 다음 단계를 거친다.
- 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝낸다. - Initial Mark
- 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다. 이 단계는 다른 Thread가 실행 중인 상태에서 동시에 진행된다. - Concurrent Mark
- Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긱 객체들을 확인한다. - Remark
- Thread를 정리한다. 이 작업도 다른 Thread가 실행되고 있는 상황에서 진행한다. - Concurrent Sweep
조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 stop-the-world 시간보다 더 오래 걸리기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인하며 신중히 사용해야 한다.
G1(Garbage First) GC
- 지금까지 소개된 GC 방식 중 가장 빠르다.
- 아직 완~전히 안정화돼있지는 않다.
Java 7에서 소개된 GC 방식이다. 장기적으로 논란이 많은 CMS GC를 대체하기 위해 만들어졌다. Young과 Old 영역이 물리적으로 나뉘어 있지 않고, 해당 영역의 객체들을 Region이라는 구역에 할당한다. 그러다가 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다. 즉, 지금까지 Young의 세가지 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC방식이다. 각 Region이 Eden, Survivor, Old 같은 영역의 역할을 바꿔가며 수행한다.
Reference
'Language > Java' 카테고리의 다른 글
[Java] Number와 String 특징 정리 - Primitive, Autoboxing, String, StringBuilder, StringBuffer (0) | 2019.09.08 |
---|---|
[JVM] Java 메모리 구조 (0) | 2019.09.05 |
[java] Thread 작업 완료 확인 - Future (0) | 2019.09.01 |
javaDoc에 샘플코드(xml 등) 작성하기 (0) | 2019.08.31 |
람다식(Lambda Expression) 아주아주 기초 문법 (0) | 2019.08.30 |
댓글