ecsimsw
두 가지 GC와 처리 영역들 본문
GC와 Stop the world
JVM의 가비지컬렉터는 힙 영역의 메모리에서 더 이상 사용되지 않는 자원을 정리하는 역할을 한다. 이때 사용되지 않는다란 다른 지역 변수, static 변수, 파라미터, JNI의 객체, 다른 힙 영역의 객체 등에서 더 이상 참조되지 않는 것을 말한다. 아래 그림에서 빨간색으로 표시된 Unreachable objects는 GC의 대상이 된다.
이때 자원을 정리하는 과정에서 새로운 객체가 할당되거나 객체 간 연결이 생길 경우를 방지하기 위해, GC를 위한 스레드를 제외한 모든 스레드의 작업이 중단된다. 이런 GC를 위한 애플리케이션 전체 중단 시간을 Stop the world라고 한다. ( 보다 자세한 STW가 필요한 이유 )
두가지 GC와 처리 영역들
Stop the world의 지속 시간과 실행 빈도를 줄이는 것이 JVM을 설계할 때 큰 과제였을 것이다.
1. 대부분의 객체는 임시적이다.
2. 오래된 객체로부터 임시적인 객체로의 참조는 적게 존재한다.
Sun JVM은 이 두 가지를 전제로 "임시적인 객체와 오래된 객체의 가비지 컬렉팅 빈도와 방식을 다르게 해도 되지 않을까?" 라는 아이디어를 내게 된다. GC가 비용이니만큼 오래되고 임시적이지 않음이 검증된 객체들은 검증 대상에서 제외하고, 새로운 객체를 검증하겠다는 것이다.
예를 들면 스파이 색출 같은 것이다. 스파이 색출을 위해서 사상 검증이 필요할 때 간단하고 짧은 시험을 YB 부원들에게 자주하여 스파이를 애초에 거르고, 큰 이벤트가 있는 상황에만 이미 많은 시험을 많이 통과한 OB 부원들까지 어렵고 오래 걸리는 시험으로 스파이를 대대적 색출하겠다는 아이디어다.
JVM은 heap 영역을 크게 Old gernerations space, Young gernerations로 나눈다. 그리고 간단하고 짧은 방식으로 YG를 자주 검사하는 가비지 컬렉터와, 빈도수는 낮지만 오래 걸리고 대대적인 검사를 진행하는 컬렉터를 나눠 효율을 높이게 된다. 이때 전자 방식의 컬렉터를 minor GC, 후자 방식의 컬렉터를 Major GC라고 이름 붙인다.
Eden 부터 살아남아 Old 영역으로 인정받기까지
생성 크기가 큰 특별한 경우를 제외하면 대부분의 객체는 Eden 영역에 생성되게 된다. 그리고 Eden 에서 Full 이 발생해 minor GC가 동작하면 가비지 객체들은 메모리에서 제거되고 살아남은 객체들은 Survivor(1) 영역으로 이동된다. 그리고 다시 minor GC가 일어나면 이 Survivor(1) 에서 살아남은 객체와 Eden에서 살아남은 객체가 다른 쪽 Survivor에 쌓이게 되고, 그렇게 빈 Survivor (1)와 가득 찬 Survivor (2)가 번갈아 객체를 담게 된다. 사실 영역 자체가 바뀌는 것은 아니다. 단순히 포인터를 바꿔 현재 저장되고 있는 Survivor를 표시한다고 한다.
이 과정을 반복하며 더 이상 Survivor에도 자리가 없을 때, 그러니까 Survivor가 가득 찰 때까지도 살아남은 객체들은 old 영역으로 이동하게 된다. 이렇게 old generations 로 인정받은 객체는 이후 minor GC의 확인 대상에서 벗어나게 되고, 이후 minor보다 적은 빈도로 발생하는 major GC에서나 확인을 받게 된다.
Minor GC와 Major GC
앞서 Minor GC와 Major GC의 서로 다른 검색 빈도와 범위를 설명했다. Stop the world는 Major GC에서만 일어난다. Minor GC는 스레드를 멈추지 않고 CPU 부하가 적으며 수행 속도도 Major GC보다 10배 이상 빠르다. JVM은 GC와 처리 영역을 나눠 신생되는 객체는 임시 객체일 확률이 크다는 것을 전제로 검증이 안된 객체들만 가벼운 GC를 더 자주 수행하는 것으로 기존 Stop the world 문제의 빈도를 줄인 것이다.
그렇다면 YG와 OG는 어떻게 나누는 것이 좋을까. 속도가 빠른 Minor GC를 위한 YG 영역을 무조건 크게 하는 것이 좋을까? 그렇지 않다. YG를 늘린다면 예상대로 Minor GC의 빈도는 적어지고 OG로 넘어가기 전 더 많은 횟수로 신입 객체들을 검증할 수 있다. 반대로 Minor GC의 검색 범위가 늘어나므로 Minor GC의 검색 시간이 늘어날 것이고, OG 넘어가는 것이 더 효율적인 객체가 YG의 공간이 너무 커 넘어가지 못하고 Minor GC의 관리 대상이 되는 횟수가 늘 것이다.
반대로 YG를 줄인다면 Minor GC의 속도는 빨라지지만 그 빈도가 많아지고 OG로 넘어가는 객체가 많아져 이번엔 Major GC의 속도에 문제가 생길 것이다.
OG의 범위도 YG 범위 설정과 마찬가지로 명확한 정답은 없다. OG를 줄이면 OOM이 일어나기 쉽고 Major GC의 빈도가 늘어 STW 빈도가 늘어날 것이다. 반대로 OG를 늘리면 Major GC의 속도가 늘어 애플리케이션 중단 시간이 문제가 될 것이다.
Java 애플리케이션 메모리를 무조건적으로 늘리는 것이 GC의 관점에선 정답이 아니라는 것도 보일 것이다. 애플리케이션 메모리를 늘리게 되면 GC의 관리 영역이 늘어나 수행 시간이 늘고, 그렇다고 줄이게 되면 OOM 문제와 GC 수행 빈도가 많아진다는 단점이 생긴다.
정답은 언제나 애플리케이션 바이 애플리케이션.
서비스 별로 메모리를 얼마를 할당하는 것이 가장 좋고, 그 메모리 안에서도 Eden 영역, Survivor 영역, Older 영역을 어떤 비율로 지정하는 것이 좋을까 모니터링과 테스트로 최적의 값을 찾아야 할 것이다.
'Language > Java, Kotlin' 카테고리의 다른 글
JitPack 으로 자바 라이브러리 배포하기 (2) | 2022.01.24 |
---|---|
Local Maven Repository 에 라이브러리 배포하기 (0) | 2022.01.22 |
Optional 로 Null 을 알리는 습관 (0) | 2021.03.15 |
HashSet의 원리 (2) | 2021.03.12 |
가독성 있는 자바 코드를 위한 나만의 규칙 (4) | 2021.03.04 |