Post

[JVM] 가비지 컬렉터

[JVM] 가비지 컬렉터

🎯 배경

지금까지 JVM에 대해 학습을 했다. 이제 마지막 gc부분만 학습하면 JVM에 대한 기본적인 내용은 다 공부한 느낌이 든다. 계획을 말하면, GC는 JVM의 힙메모리에서 동작을 하게 된다. 그러면 저번장에서 살펴본 힙메모리를 토대로 GC를 학습할 예정이다.

종류

저번장에서 힙메모리에 대해 학습을 하였다. 간략하게 복습하자면 힙메모리는 young(eden, survivor space)과 old generation으로 나뉘어져있다. 그리고 시간이 지나면서 다음 영역으로 이동(복사)되는 형태라고 할 수 있다.

저건 기본적인 방식이고, GC에는 수많은 알고리즘들이 존재한다. 그것에 대해 자세히 알아보자.

Serial GC

이 방식은 가장 기본적인 방식이며, 단일 스레드로 동작을 한다고 한다. 즉, 객체는 하나만 처리가 가능하다는 뜻이 된다. 초창기에 만들었던 방식이었고 그때 당시에는 cpu가 1~2코어가 대부분이었다고 한다.

stop the world
GC가 객체 간의 참조 관계를 정확하게 분석하기 위해 모든 애플리케이션 스레드의 실행을 강제로 정지시키는 구간이다. 이는 모든 GC 알고리즘에서 발생할 수 있는 기본 메커니즘이며, GC의 정확성을 보장하기 위한 필수 동작이다.

그러나 STW가 길어질수록 서비스 응답 지연, 프리즈 현상, 장애 리스크가 커지기 때문에, 현대 GC 알고리즘은 STW 시간을 줄이거나 일부 구간만 STW로 처리하는 방식으로 진화하고 있다.

Mark → Sweep → Compact 방식
Old영역에서 모든 객체를 위 방식으로 처리. 기본적인 처리 방식으로

Serial GC

그림을 보면 알 수 있듯이, 쓰레드가 한개이기 때문에 빨간선으로 표기하였고 죽은 객체를 노란색으로 그리고 압축을 검정색 화살표로 표현하였다. 지금까지 가장 간단할수 있는 serial gc를 알아봤다. 근데 이 방식에는 아주 치명적인 문제가 있었는데 쓰레드는 하나만 사용할 수 있다는 점이었다. 그림을 보면 알겠지만 쓰레드에 해당되지 않는 객체들도 존재한다. 저 객체들은 Serial GC에서는 현 작업을 마무리를 지어야 진행할 수 있다.

그래서 나온 알고리즘이

Parallel GC

Parallel GC다. 위에서 stop the world와 mark -> sweep -> compact 방식에 대해 설명했기때문에 이들을 제외하고 설명할 예정이다.

Parallel GC

그림을 보면 쓰레드가 늘어났다. 이제 다른 로우의 객체들도 처리가 가능해졌다. 결국 속도는 빨라졌곘지. java8부터 기본으로 사용하는 방식있다.(gpt한테 물어보니 sever jvm할때는 java7이전에도 썼다는데 요건 패스하자.)

전제척인 수행속도가 향상은 되었지만 여전히 STW에 기반 pluse time은 줄어들지 않는다. (이것을 다시 설명하면 STW될때 잠시동안 일시 정지 되는 현상이다.)

이걸 해결하는 알고리즘은 없을까?

G1GC

다음으로 나온 알고리즘은 G1GC다. 이거는 이름으로 부터 추측할 수 있는데 Garbage-First GC라고 한다. 즉, 가장 많은 쓰레기를 포함한 영역부터 우선적으로 수거”하는 GC 알고리즘이라 할 수 있다. 이거는 java9부터 기본이 되었다고 한다. 원래 이전에 CMS라는 알고리즘이 있긴하지만 그 친구는 default였던적이 없었기 때문에 추후에 알아보자.

이거 같은 경우 eden -> survivor space -> old generation 과정에 gc도 함께 들어간 형태로 이해를 하였다.

과정은 다음과 같다. Initial -> Concurrent Mark -> Remark -> Cleanup -> Mixed GC -> (필요 시 Full GC)

Initial

Initial부터 확인해보자. G1GC

g1gc는 다른 알고리즘과 달리 그림을 여러개 그려야 할거 같다. 파란색 부분이 region부분이고 이 영역에서 gc가 발생한다고 생각하면 된다.

여기에서 느낀점은 굉장히 intial은 기존의 mark방식과 유사하다고 느꼈다. 결국, 여기에서 살아있는 객체와 죽어있는객체를 비교하는 과정이다. 여기에서는 GC ROOT에 직접적으로 연결된 객체로만 진행이 된다고 한다. 여기에서 SWT가 발생하지만 GC ROOT만 마킹이 되기때문에 굉장히 짧다.

gc root란?
GC ROOT는 가비지 컬렉션의 시작점으로, Java 프로그램에서 살아있는 객체들을 추적하는 데 사용

자 이렇게 마킹을 해놓고 다음 과정인 concurrent mark를 확인해보자.

Concurrent Mark

조금전에 gc root인것만 마킹이 되어진다. 하지만 그 하위 객체들은 마킹을 하지 않았다. 즉, 참조만 되어있는 객체들은 마킹이 되지 않았다는 이야기인데 여기에서 진행이 되어진다.

G1GC

gc root부터 시작해서 더 이상 사용하지 않는 객체들을 마킹이 되어진다. 이 과정은 추가적으로 마킹하는 과정이기때문에 서비스 중단없이 가능하다. 즉, STW는 발생하지 않는다.

여기에서는 위에서 보여줬던 그림에서 동시에 발생하다는점 기억하면 될거 같다.

Remark

G1GC

여기에서는 concurrent mark에서 마킹이 되어진 객체들을 다시 한번 확인하는 과정이다. 혹시 실수한게 없는지 확인하는 절차를 갖는다. 여기에서는 STW가 발생이 되어진다. 그 이유는 정합도의 문제가 있는데 remark에서는 좀더 꼼꼼하게 처가 되어진다.

Cleanup

이제 cleanup을 통해 사용하지 않는 객체들을 수거한다. 이 과정을 기존 과정에 비유하자면 sweep과정이라 할 수 있다. 이때도 SWT는 발생하는데 짧게 발생이 된다고 한다.

여기에서는 region을 정리하는 과정이라고 한다.

Mixed GC

이 단계에서는 Region에서 Sweep/Compaction을 동시에 진행한다.

몇단계가 걸쳐서 설명을 했는데 생각보다 잘 이해가 되지 않는거 같다. 조금더 생각해보고 반영해야할거 같다. 다른 알고리즘은 추후에 알아보자. 그리고 내가 빼먹은게 힙모메리 부분에서 mataspace 영역은 설명하지 않았다. 이건 나중에 정리를 하면 좋을 거 같고, 깜빡헀다.

여기까지 gc에 대해 간략하게 알아보았다. 이제 JVM은 잠시 멈추고 쓰레드를 공부해보자.

📝 요약 (One-liner)

JVM의 가비지 컬렉션(GC)은 힙 메모리를 관리하는 핵심 기능으로, Serial GC(단일 스레드, 전체 멈춤), Parallel GC(여러 스레드로 병렬 처리), 그리고 최신의 G1GC(여러 Region을 나눠 단계별로 마킹과 청소를 수행, 짧은 STW와 효율적 메모리 관리) 등 다양한 방식이 있다. 각 GC는 서비스 중단 최소화와 성능 향상을 목표로 진화해왔으며, G1GC는 Young/Old 세대 구분 없이 Region 단위로 선택적으로 GC를 수행해 현대 JVM의 기본이 되고 있다.

This post is licensed under CC BY 4.0 by the author.