티스토리 뷰
본 게시물은 해당 블로그에서 레퍼런스를 번역한 자료임을 알립니다.
퍼가실때 해당 블로그의 주소는 밝히실 필요가 없으나 레퍼런스의 주소는 밝혀주시면 감사하겠습니다.
자바 9 버전 이후부터 G1 GC가 디폴트 GC로 설정되었다.
(역주: 자바 8까지는 패러렐 GC가 디폴트였고 자바 18인 현재까지 G1 GC가 디폴트 GC로 사용된다. 만약 싱글 코어라면 시리얼 GC가 디폴트로 설정된다.)
G1 GC는 자바 7에 처음 도입되어 다른 GC와는 다른 매우 큰 힙 영역을 효과적이면서 동시에 다루는데에 특화되어 있다. 또한 최대 중단 시간을 넘지 않도록 설정할 수 있다. G1 GC가 다른 GC와 어떻게 다르게 동작하는지 확인하고 또 어떻게 G1 GC가 어떻게 큰 크기의 힙 영역을 다룰 때 다른 GC를 압도하는지 알아보자.
G1 GC는 어떻게 동작할까?
대부분의 현대의 GC는 힙 영역의 객체를 young 영역과 old 영역 객체 둘 중에 하나로만 나눈다. 이것은 90%가 넘는 객체가 맨 처음 GC에서 살아남지 못 한다는 현대의 자바 애플리케이션에 대한 연구를 기반으로 한다. 좀 더 오래된 객체들(몇 번의 GC에서 살아남은)은 98%의 확률로 계속 살아있을 확률을 가진다.
자바 GC는 young 영역에서 한 발 더 나아가 Eden 영역과 survivor 영역으로 나눈다. 새롭게 할당된 객체는 언제나 항상 G1 Eden 영역으로 할당되는데 객체가 맨 처음 GC에서 살아남으면 survivor 영역으로 옮겨진다. 이러한 과정에서 객체가 여러 번의 GC에서도 살아남으면 그때 마침내 old 영역으로 옮겨지게 된다.
이와 같이 생존 시간에 따라 객체를 분류하는 이유는 런타임시 효율적인 알고리즘을 새로운 객체에 적용하고 메모리 효율적인 알고리즘을 old 영역에 적용하여 메모리를 효율적으로 관리하기 위함이다.
(런타임의 알고리즘은 살아있는 객체의 숫자에만 연관이 있는 대신 힙 영역의 절반을 손해본다)
(메모리 알고리즘은 힙 영역의 크기에만 연관이 있는 대신 사용 가능한 메모리에서만 최대한 효율적으로 사용하려 한다)
이러한 GC의 힙 영역은 다음과 같이 묘사할 수 있다.
G1 GC의 장점
다른 GC와 비교했을때 G1은 크게 두 가지의 장점이 있다. 첫 번째로는 GC의 대부분 작업을 애플리케이션의 스레드를 멈추지 않고도 동시에 진행할 수 있으며 두 번째로는 연속적으로 공간을 가지는 것이 아니라는 점에서 매우 큰 힙 영역을 다루는데에 있어 효율적이다.
게다가 G1 GC는 young 영역과 old 영역을 한 번에 GC할 수 있는데 이는 G1 GC가 가용 가능한 힙 영역을 사용하는 고유한 방법에 관련이 있다. 대부분의 GC처럼 힙 영역을 크게 3 영역(Eden, survivor, old)으로 나누는 대신에 G1 GC는 힙 영역을 수많은 (심지어 수 백개의) 작은 영역으로 쪼갠다.
이런 영역들의 크기는 고정되어 있고 (디폴트로는 2MB) 모든 영역은 어떠한 공간을 가지게 된다. G1의 힙 영역은 다음과 같이 생겼다.
이렇게 힙 영역을 잘게 나누는 것은 G1의 작업이 빠르게 끝나는 것을 가능하게 한다. 만약 한 영역이 GC에 의해 정리된다면 그 영역의 GC되고 나서 살아있는 객체들의 카피는 unassigned 영역으로 옮겨지게 된다.
해당 영역이 Eden 영역이라고 한다면 그 영역에서 살아남은 객체가 할당되는 unassigned 영역은 survivor 영역이 되는 것이다. 해당 영역의 어떤 객체도 살아남지 못 하는 이상적인 경우, 해당 영역을 unassigned로 선언하고 나면 어떤 추가 작업도 할 필요가 없어진다.
만약 어떤 개발자가 힙 영역의 전체를 GC하길 원한다면 G1 GC도 여타 GC와 같이 똑같은 양의 작업을 해야 되는 것은 사실이지만 G1 GC는 애초에 힙 영역의 전체를 GC 할 일이 없다는 점에서 다른 GC와의 차이점을 가진다.
G1은 모든 영역을 전부 훑을 필요가 없다. 단지 훑어야 하는 영역의 개수를 선택해서 하면 된다. 이때 G1 GC는 다음 객체의 메모리 할당을 위해 여유 메모리가 필요하다면 언제나 꽉 찼거나 거의 다 찬 영역만을 대상으로 GC를 진행하여 시간 최적화를 이뤄낸다.
다른 GC는 언제나 전체 영역을 훑어야 하기에 시간 복잡도는 힙 영역의 크기와 비례를 하게 된다. G1 GC는 전체 영역을 다룰 필요가 없기에 시간 복잡도는 살아있는 객체의 개수에만 비례를 한다. 만약 힙 영역이 이상적일만큼 거대하다면, 몇 몇 영역은 언제나 가비지로 꽉 차있을 것이므로 단순히 그 영역만을 GC하면 된다.
심지어 G1 GC는 대부분의 작업이 동시에 진행된다. 우리는 이미 자바에는 Concurrent Mark & Sweep GC (CMS)라고 하는 동시에 진행하는 GC가 있다는 것을 알고 있다. 하지만 CMS는 old 영역만을 동시에 진행하며 young 영역을 GC하기 위해서는 여전히 애플리케이션 전체를 멈추어야 한다.
G1 GC는 객체들을 마크하기 위해 GC를 시작하는 순간에만 애플리케이션을 멈추게 된다. 이 과정을 "최초의 마킹(Initial Mark)"라 부른다. 그리고 애플리케이션이 동작하는 동안에 GC는 참조 구조를 따라가며 객체들의 상태를 마킹하게 되고("동시에 진행되는 마킹(Concurrent Mark)" 과정) 이 과정이 끝나면 애플리케이션을 다시 멈추고 마지막 마킹을 한 이후 ("최종 마킹(Final Mark)" 과정) GC 할 영역을 고르고 청소하게 된다("대청소(Evacuation)" 과정).
이때 이 대청소 과정이 매우 빠르기 진행되기에 (특히 큰 힙 영역일 때) G1 GC가 다른 GC에 비해 애플리케이션의 동작 중단 시간의 측면에서 압도적으로 좋은 성능을 발휘한다.
G1 GC의 단점
G1 GC의 단점은 힙 영역이 작을수록 별로 좋은 성능을 발휘하지 못 한다는 점이다. G1 GC를 사용해도 괜찮은 적절한 힙 영역의 크기에 대한 것은 당신의 애플리케이션에 달려있다. 필요시에는 언제나 -XX:+UseParallelOldGC 플래그를 이용해서 이전의 GC로 설정할 수 있다. 만약 G1 GC의 가용 가능한 힙 영역이 너무 작다면 GC 로그에서 "Full GC"를 보게 될 것이다. Full GC는 오직 위에서 설명한 일반적인 G1 GC 과정으로 메모리 관리가 부족하다고 판단될 때에만 진행된다. 이 경우에는 메모리 효율적이지만 느린 알고리즘을 이용하여 힙 영역 전체를 GC하게 되고 다음 GC에서는 더 좋게 상황을 만들 수 있다. 만약 Full GC가 일어난다면 가능하다면 힙 영역의 크기를 늘리거나 아예 다른 GC를 선택하는 것이 더 좋을 수 있다.
작은 영역의 경우라면 -XX:MaxGCPauseMillis=n 설정을 통해 최대 중단 시간을 설정할 수 있다. 이렇게 하면 G1 GC는 이전 GC에서의 최대 한도를 넘지 않으면서도 측정된 가비지의 개수를 넘지 않는 한 번에 GC 할 수 있는 영역의 최대 개수를 측정한다. G1 GC 또한 실시간 GC와는 여전히 거리가 멀지만 힙 영역의 구조가 고정되어 있어 최대치를 지정할 수 없는 다른 GC보다는 더 좋은 성능을 낸다.
레퍼런스
https://www.dynatrace.com/news/blog/understanding-g1-garbage-collector-java-9/
'CS > Java' 카테고리의 다른 글
자바 8, 11, 17 버전별 기능 차이 (0) | 2022.08.26 |
---|---|
자바 정규표현식 (0) | 2022.08.13 |
자바의 GC가 어떻게 동작하는지 알아보자 (0) | 2022.08.07 |
자바에서 String은 왜 Immutable일까? (0) | 2022.08.02 |
자바의 equals()와 hashCode() (0) | 2022.08.02 |
- Total
- Today
- Yesterday
- 도커
- 레디스
- ModelAttribute
- RequestParam
- vim
- RequestBody
- 루나빔
- lunarvim
- RequestPart
- 배포
- Dap
- 아키텍처
- JavaScript
- IDE
- neovim
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |