❗️❗️ 잘못된 정보는 알려주시면 감사합니다!
부끄럽게도 자바를 공부하면서 Garbage Collection/Garbage Collector 둘의 차이를 몰랐었다.. 그다지 중요하지 않다고 생각해서 그냥 넘어갔던 것 같다.
GC를 공부하다 보면 왜 GC에 대해서 잘 알고 있어야 하는지 알게 된다.
GC를 자주 하게 되거나 GC 영역이 클 경우에는 GC로 인해서 여러 가지 이슈가 발생할 수 있다. 뒤에 따로 설명하겠지만 GC를 실행할 동안에는 모든 스레드가 작업을 멈추고 GC 작업을 수행하는 데 이때 내 프로그램은 멈춰있게 된다. 만약 내가 운영하는 웹서버에 GC가 일어날 때마다 서버가 멈추게 되고 이를테면 트래픽이 몰리는 시간에 이런 일이 일어났다가는 과부하로 인해 장애가 날 수도 있다. 이외에도 여러 가지 성능상 문제가 존재하게 되는데 GC의 수집 조건을 정확하게 알지 못하면 GC가 제거하지 못하는 객체로 인해 OOM이 발생할 수도 있다. 이와 같은 실수를 하지 않기 위해서는 GC에 대해서 잘 알고 있어야 한다.
보통 GC는 Garbage Collection을 말한다.
가비지컬렉터가 가비지 컬렉션을 수행한다.
메모리라는 것은 한정된 자원이다 누군가는 이 메모리를 관리해야 된다. 처음에는 이 메모리 관리를 개발자가 직접 했다.
대학교 C언어 시간에 한 번쯤을 봤을 법한 malloc/free 함수들이 바로 이 메모리를 관리하는 함수들이다 이 함수를 이용해서 개발자가 직접 메모리를 할당 해제하면서 관리를 했다. 생각해 보자 간단한 프로젝트 하나를 하는데도 객체들을 몇 개나 만들어내며 일일이 그것들을 하나씩 할당/해제를 한다면 이 얼마나 귀찮고 불편한 작업일까??
그렇다고 해서 메모리를 할당받고 해제를 하지 않는다면 아주 큰 문제가 생긴다. 바로 메모리 누수(memory leak)라는 문제다. 이 메모리 누수가 발생해서 누적된다면 결국 프로그램은 OutOfMemory
를 뿜으면서 종료될 것이다.
이쯤 되면 GC가 필요한 이유를 알 수 있다.
이 귀찮은 작업들을 바로 GC가 대신 처리해 주는 것이다. GC의 장단점을 정리해 보자
(물론 GC를 사용하더라도 개발자의 실수로 인해서 메모리 누수는 여전히 발생할 수 있다.)
클릭시 영상 재생
클릭시 영상 재생
Stoptheworld
상태가 되고 minor gc (young gc)
가 일어난다. (이때 Mark and Sweep
방식으로 일어나게 됨)Promotion
이라고 한다.major gc(full gc)
가 발생하게 되는데 마찬가지로 Stoptheworld
상태가 되고 Mark and Sweep
방식에 compact
를 추가해서 실행된다. (메모리 단편화 제거)번역하면 세상을 멈추다 이런 뜻이 된다. 자바에서는 GC를 수행하기 위해서 GC 수행 외에 모든 스레드 작업을 멈추고 GC를 수행한 후 다시 이어서 작업을 수행한다. 이때 모든 작업을 멈추는 것을 Stop-the-world 라고 하는데
GC 과정중에 객체를 재배치하는 과정있는데 이때 객체 그래프가 일치하지 않기 때문에 Stop-the-world가 필요하다고 한다.
또 Stop-the-world가 길어지면 장애가 발생할 수 있겠다고 유추할 수 있다. 대개 GC 튜닝은 GC Time을 줄이는 게 목적이다
GC 알고리즘 중 Reference Counting라는 알고리즘이 있는데 이는 순환 참조라는 문제가 있었기 때문에
보통 Mark and Sweep(compact) 알고리즘 방식을 이용한 GC가 사용된다.
2. 마크되지 않는 객체들은 삭제시킨다(Sweep)
3. 빈 공간들이 생기지 않도록(메모리 단편화) 객체를 재배치 시킨다.(compact)
영상을 보면 Allocation Failure(할당 실패)은 영역이 꽉 찼을 때 공간을 확보하기 위해 GC가 일어나는 이유를 설명해 주고 있다.
Ergonomics는 Serial GC에서는 일어나지 않지만 Parallel부터는 Ergonomics라는 이유의 GC가 일어나는데 이는 JVM이 판단해서 현재 GC가 적합할 때라고 판단해서 GC 하는 경우를 나타낸다 그래서 old 영역이 꽉 차지 않았을 때도 GC 가 발생하는 것을 볼 수 있다.
heap의 young 영역에서는 GC 시에 Mark and Sweep 방식으로 GC가 일어나지만 compact 작업은 일어나지 않는다 따라서 메모리 단편 화가 발생한다 이때 GC 후에 영역을 옮기면서 재배치를 시키는데 이때 메모리 단편화를 제거해 준다.
이런 식으로 계속 객체들을 재배치시키면서 단편화를 제거시킨다.
영상 속 old 영역을 보면 GC가 되더라도 계속 차는 걸 볼 수 있다. 의도적으로 OOM 되도록 코드를 짰기 때문이다.
"사용하지 않는 객체들은 GC가 알아서 제거해 준다면서요!" 맞다 근데 조건이 있다.
참조하지 않는 객체를 사용하지 않는 객체로 판단하는 것이지 참조는 되고 있는데 사용하지 않는 객체들은 사용할 수도 있다고 판단하기 때문에 GC 대상이 아니게 된다. 즉 개발자가 실수로 사용하지 않는 객체의 참조를 그대로 놔둔다면 그 객체는 계속 살아있게 된다 예를 들면 Map 내부에 캐시 된 객체들을 생각할 수 있다. 캐시를 사용하지 않을 때 그때그때 제거하거나 캐시 크기를 지정해놓지 않는다면 메모리 누수가 발생할 것이다.
상대적으로 oung GC에 비해서 GC 영역이 크기 때문이다.
만약 old 영역을 작게한다면 GC의 속도는 빨라질 수 있지만 GC의 빈도가 자주 발생할 수 있다.
Ref
https://stackoverflow.com/questions/10695298/java-gc-why-two-survivor-regions
https://stackoverflow.com/questions/16695874/why-does-the-jvm-full-gc-need-to-stop-the-world
https://plumbr.io/handbook/garbage-collection-algorithms-implementations/parallel-gc
https://www.youtube.com/watch?v=Fe3TVCEJhzo&t=233s