- 자바 프로그래밍에서 하나의 객체는 메모리를 점유하고, 필요하지 않으면 메모리에서 해제되어야 한다.
- GC란 더 이상 필요가 없는 쓰레기 객체를 정리하는 작업을 말한다.
- 자바에서는 메모리를 GC로 관리하기 때문에, 개발자가 메모리를 처리하기 위한 로직을 만들 필요가 없다.
- GC 작업을 하는 가비지 콜렉터는 메모리 할당, 사용 중인 메모리 인식, 사용하지 않는 메모리 인식과 같은 일들을 수행한다.
- 사용하지 않는 메모리를 인식하는 작업을 수행하지 않으면, 할당한 메모리 영역이 꽉 차서 JVM에 행이 걸리거나, 더 많은 메모리를 할당하려는 현상이 발생한다.
- 행이란, 서버가 요청을 처리 못하고 있는 상태를 의미한다.
- 만약 JVM의 최대 메모리 크기를 지정해서 전부 사용한 다음, GC를 해도 더 이상 사용 가능한 메모리 영역이 없는데 계속 메모리를 할당하려고 하면 OutOfMemoryError가 발생하여 JVM이 다운될 수도 있다.
JAVA 메모리 영역
- 자바의 메모리 영역은 Heap 메모리와 Non-heap 메모리로 나뉜다.
- GC가 발생하는 부분은 힙 영역으로 나머지 영역은 GC 대상이 아니다.
Non-heap 메모리
- 자바의 내부 처리를 위해서 필요한 영역
메모리 영역 | 설명 |
메서드 영역 | 모든 JVM이 스레드에서 공유하는 영역으로, 런타임 상수 풀, 메서드 데이터, 메서드와 생성자 코드를 포함한다. |
JVM 스택 | 스레드가 시작할 때 JVM 스택이 생성되며, 메서드가 호출되는 정보인 프레임과 지역 변수, 메서드 임시 결과, 메서드 수행과 리턴에 관련된 정보를 포함한다. |
네이티브 메서드 스택 | 자바 코드가 아닌 다른 언어로 된 코드들이 실행될 때 필요한 스택 정보를 관리한다. |
PC 레지스터 | 자바스 스레드들은 각자의 PC 레지스터를 가지며, 네이티브한 코드를 제외한 모든 자바 코드들이 수행될 때 JVM의 인스트럭션 주소를 PC 레지스터에 보관한다. |
※ 참고: 힙 영역과 메서드 영역은 JVM이 시작될 때 생성된다.
Heap 메모리
- 클래스 인스턴스와 배열이 저장되는 영역
- 공유 메모리라고도 불리며, 여러 스레드에서 공유하는 데이터들이 저장되는 메모리이다.
- 힙 영역은 크게 Young, Old, Perm 세 영역으로 나뉘며, 이 중 Perm 영역은 거의 사용되지 않는 영역이다.
- Young 영역은 다시 Eden 영역과 두 개의 Survivor 영역으로 나뉜다.
객체의 이동
- 일단 메모리에 객체가 생성되면 Eden 영역에 객체가 지정된다.
- Eden 영역에 데이터가 꽉 차면 객체들은 Survivor 영역 중 비어있는 영역으로 이동한다.
- Survivor 영역 사이에는 우선순위가 없으며, 두 개의 영역 중 하나는 반드시 비어 있어야 한다.
- 할당된 Survivor 영역이 차면, GC가 되면서 Edem 영역에 있는 객체와 꽉 찬 Survivor 영역에 있는 객체가 비어 있는 Survivor 영역으로 이동한다.
- 이러한 작업을 반복하면서, Survivor 1과 2를 왔다갔다 하던 객체들은 Old 영역으로 이동한다.
- 만약 Young 영역에 있는 객체 중 크기가 Survivor 영역보다 큰 경우에는, Survivor 영역을 거치지 않고 바로 Old 영역으로 넘어간다.
GC의 종류
- GC는 크게 마이너 GC와 메이저 GC 두 가지 타입으로 나뉜다.
- 마이너 GC: Young 영역에서 발생하는 GC
- 메이저 GC: Old 영역이나 Perm 영역에서 발생하는 GC
- 이 두 가지 GC가 어떻게 상호작용 하느냐에 따라서 GC 방식에 차이가 나며, GC가 발생하거나 객체가 각 영역에서 다른 영역으로 이동할 때, 애플리케이션의 병목이 발생하면서 성능에 영향을 준다.
GC의 방식
1. 시리얼 콜렉터
- Young 영역과 Old 영역이 연속적으로 처리되며, 하나의 CPU를 사용한다.
- 콜렉션이 수행될 때 애플리케이션 수행이 정지되기 때문에, Stop-the-world라고 표현한다.
- 살아있는 객체들은 Eden 영역에 존재한다.
- Eden 영역이 꽉 차게 되면 To Survivor 영역으로 살아있는 객체가 이동한다. 이때 Survivor 영역에 들어가기에 너무 큰 객체는 바로 Old 영역으로 이동한다. From Survivor 영역에 살아있는 객체는 To Survivor 영역으로 이동한다.
- To Suvivor 영역이 꽉 찬 경우, Eden 영역이나 From Survivor 영역에 남아있는 객체들은 Old 영역으로 이동한다.
- 이후 Old 영역이나 Perm 영역에 있는 객체들은 Mark-sweep-compact 콜렉션 알고리즘을 따른다.
- Mark-sweep-compact 콜렉션 알고리즘은 쓰이지 않는 객체를 표시해서 삭제하고 한 곳으로 모으는 알고리즘으로, 아래와 같이 수행된다.
- Mark: Old 영역으로 이동된 객체들 중 살아있는 객체를 식별한다.
- Sweep: Old 영역의 객체들을 훑는 작업을 수행하여 쓰레기 객체를 식별한다.
- Compact: 필요없는 객체들을 지우고 살아있는 객체들을 한 곳으로 모은다.
- 시리얼 콜렉터는 대기시간이 많아도 크게 문제되지 않는 시스템에서 사용되며, 일반적으로 클라이언트 종류의 장비에서 많이 사용되는 방식이다.
2. 병렬 콜렉터
- 스루풋 콜렉터로도 알려진 방식으로, 다른 CPU가 대기 상태로 남아있는 것을 최소화하는 것이 목적
- 시리얼 콜렉터와 달리 Young 영역에서의 콜렉션을 병렬로 처리한다.
- 많은 CPU를 사용하기 때문에 GC의 부하를 줄이고 애플리케이션의 처리량을 증가시킬 수 있다.
- Old 영역의 GC는 시리얼 콜렉터와 마찬가지로 Mark-sweep-compact 콜렉션 알고리즘을 사용한다.
3. 병렬 콤팩팅 콜렉터
- 병렬 콜렉터와 동일하게 이 방식도 여러 CPU를 사용하는 서버에 적합하며, 병렬 콜렉터와의 차이점은 Old 영역 GC가 아래와 같은 알고리즘을 사용한다는 것
- Mark: 살아있는 객체를 식별하여 표시해 놓는다.
- Summary: 이전에 GC가 수행되어 컴팩션된 영역에 살아있는 객체의 위치를 조사한다.
- Compact: 컴팩션을 수행하는 단계로, 수행 이후에는 컴팩션된 영역과 비어있는 영역으로 나뉜다.
- Sweep 단계는 단일 스레드가 Old 여역 전체를 훑지만, Summary 단계는 여러 스레드가 Old 영역을 분리하여 훑는다.
- 또한, 앞서 진행된 GC에서 컴팩션된 영역을 별도로 훑는다는 점도 다르다.
4. CMS 콜렉터
- 로우 레이턴시 콜렉터로 알려져 있으며, 힙 메모리 영역의 크기가 클 때 적합하다.
- Young 영역에 대한 GC는 병렬 콜렉터와 동일하며, Old 영역의 GC는 아래의 단계를 거친다.
- Initail Mark: 매우 짧은 대기시간으로 살아있는 객체를 찾는다.
- Concurrent Mark: 서버 수행과 동시에 살아있는 객체에 표시해 놓는다.
- Remark: 컨커런트 표시 단계에서 표시하는 동안 변경된 객체에 대해서 다시 표시한다.
- Concurrent Sweep: 표시되어 있는 쓰레기 객체를 정리한다.
- CMS는 컴팩션 단계를 거치지 않기 때문에 왼쪽으로 메모리를 몰아 놓는 작업을 수행하지 않는다.
- 따라서, 빈 공간이 발생하므로, Old 영역의 %를 지정해줄 수 있다. (기본값은 68)
- CMS 콜렉터 방식은 2개 이상의 프로세서를 사용하는 서버에 적당하며, 가장 적당한 대상으로는 웹 서버가 있다.
- 추가적인 옵션으로 Young 영역의 GC를 더 잘게 쪼개어 서버의 대기시간을 줄일 수 있으며, CPU가 많지 않고 시스템의 대기시간이 짧아야할 때 사용하면 좋다.
5. G1 콜렉터
- G1은 CMS GC의 단점을 보완하기 위해서 만들어 졌으며, GC 성능도 매우 빠르다.
- G1은 바둑판 모양으로 생겼으며, 각 바둑판의 사각형을 region이라고 한다.
- Young 영역과 Old 영역이 물리적으로 나뉘어있지 않고, 각 구역의 크기는 모두 동일하다.
- 바둑판 모양의 구역은 각각 Eden, Survivor, Old 영역의 역할을 변경해 가면서 수행되며, Humongous 영역도 포함한다.
- G1은 아래의 과정을 통해 Young GC를 수행한다.
- 몇 개의 구역을 선정하여 Young 영역으로 지정한다.
- 지정된 Linear 하지않은 구역에 객체가 생성되면서 데이터가 쌓인다.
- Young 영역으로 할당된 구역에 데이터가 꽉 차면, GC를 수행한다.
- GC를 수행하면서 살아있는 객체들만 Survivor 구역으로 이동시킨다.
- 살아남은 객체들이 이동된 구역은 새로운 Survivor 영역이 되며, 다음 Young GC가 발생하면 Survivor 영역에 계속 쌓는다.
- Survivor 영역에 있는 객체가 몇 번의 Young GC 후에도 살아있으면, Old 영역으로 승격된다.
- G1의 Old 영역 GC는 CMS GC 방식과 비슷하며, 아래 여섯 단계로 진행된다.
- Initail Mark: Old 영역에 있는 객체에서 Survivor 영역의 객체를 참조하고 있는 객체들을 표시한다.
- Root Region Scanning: Young GC가 발생하기 전, Old 영역 참조를 위해서 Survivor 영역을 훑는다.
- Concurrent Mark: 전체 힙 영역에 살아있는 객체를 찾는다. 만약 이때 Young GC가 발생하면 잠시 멈춘다.
- Remark: 힙에 살아있는 객체들의 표시 작업을 완료한다. 이때 SATB라는 알고리즘을 사용하며, 이는 CMS GC에서 사용하는 방식보다 빠르다.
- Cleaning: 살아있는 객체와 비어있는 구역을 식별하고, 필요없는 객체들을 지우고, 비어있는 구역을 초기화한다.
- Space Reclamation: 살아있는 객체들을 비어있는 구역으로 모은다.
'Java > Java Tuning' 카테고리의 다른 글
reflection (0) | 2022.03.28 |
---|---|
JVM (Java Virtual Machine) (0) | 2022.01.31 |
서버 세팅 (0) | 2022.01.22 |