가비지수집(GC)는 Heap영역에서 사용하지않는 객체를 삭제하는 프로세스를 말한다.
시스템에있는 모든 객체의 수명을 정확히 몰라도
런타임이 객체를 추적하며 쓸모없는 객체를 알아서 제거하는것이 핵심이다.
자동회수한 메모리는 깨끗이 비우고 재활용할수있다.
GC에서 중요한 두가지 원칙
JVM의 런타임 데이터영역(메모리영역)
유효한 참조여부를 파악하기위해 항상 유효한 최초의 참조가 있어야하는데 이를 객체참조의 root set이라고한다.
힙에있는 객체들의 참조는 다음 4가지경우에의해 발생한다
root set이 참조하고있는녀석 , 아닌녀석을 구분한다.
그림: https://d2.naver.com/helloworld/329631
root set 에서 시작되어 참조되고있는관계는Reachable , 아닌녀석은 Unreachable 하다고 표현한다 .
어디에서도 참조되지않고있는녀석은 Unreachable 한데, 이녀석은 GC의 수거대상이 된다.
Mark 과정 = GC Root로부터 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고있는지 찾아서 마킹한다.
Reachable, Unreachable 객체를 식별 하는과정
Sweep 과정 = Unreachable 객체들을 Heap에서 제거한다.
Compact 과정 = Sweep 후에 분산된 객체들을 Heap의 시작주소를 한곳으로모아 메모리단편화를 막아주는과정(Optional)
의도적으로 GC를 실행시켜야한다
=> 실행중인 어플리케이션이 GC에게 리소스를 내줘야하는 상황이 생길수있다.
애플리케이션 실행과 GC실행이 병행된다.
Young Generation영역
Old Generation영역
Young Generation 에서 오랫동안 살아남은 객체들이 존재하는 영역
metaspace
가비지 컬렉션시에 필요한 클래스와 메소드의 요약 정보가 존재하는영역
#1 새로운 객체가 Eden 영역에 할당한다 -> 그렇게 Eden영역이 꽉차면 Minor GC가 발생한다.
#2 Mark And Sweep 과정이 발생한다 -> 살아남은 객체들은 Survivor 영역으로 이동한다.
Survivor 영역은 2곳이 있는데 둘중에하나는 반드시 비어있어야한다.
#3 살아남은 객체들은 age값이 증가한다 -> 다시 Eden영역에 새로운 객체가 할당된다 -> 다시 Minor GC발생 -> Survivor로 이동한다.
Survivor 0이 꽉차면 Survivor 에서도 Mark-Sweep 이 일어나고, 0에서 살아남은 녀석들은 Survivor 1로 이동한다.
-> Survivor이 또 꽉차면 Mark-sweep 해서 Survivor 0 으로 이동한다 -> 반복
#4 그런데 특정 객체는 age가 계속 올라 임계점에 다다를수있는데 그때 Old Generation 으로 이동한다.(Promotion과정이라고한다)
-> 임계점에 다다른 객체들로 Old Generation 도 꽉차게 되면 Major GC가 발생한다.
가비지를 수집하는 힙은 단명객체를 쉽고 빠르게 수집할수있게 설계해야하며, 장수 객체와 단명 객체를 완전히 떼어놓는게 가장좋다
gc설계자들이 애플리케이션 분석해보니 대부분 객체 수명이 짧다.
대부분객체가 금방사라지니 young 영역을 어떻게 다루느냐에따라 성능이 좌지우지 되었던것이다.
일반적으로 애플리케이션에서 사용되는객체는 오래유지되는객체보다 잠시 사용되는경우가 많다.
STW(Stop-the-world)
GC사이클이 발생하여 가비지를 수집하는 동안에는 모든 애플리케이션 스레드가 중단된다는 의미.
한마디로 GC를 실행하기위해 JVM이 애플리케이션 실행을 멈추는것.
=>싱글쓰레드환경및 Heap영역이 매우작을때 사용
Humonogous : Region 크기의 50%를 초과하는 큰 객체를 저장하기 위한 공간
Avaliable/Unused : 사용가능한(아직 사용되지않은) 공간
Young영역에 있는 Reachable객체들을 서바이벌 영역이나 old영역으로 copy or move 하고 Eden영역을 Available Region으로 돌리는 과정이 일어난다
Old Region 에 존재하는 객체들이 참조하는 Survivor Region을 찾는다 (STW 발생)
Root Region Scan: 위에 찾은 Survivor 객체들에대한 스캔작업을 한다
Concurrent Mark : 전체 Heap의 스캔작업을 하고, GC대상 객체가 발견되지않은 Region은 이후 단계를 제외한다
Remark: STW해두고 최종적으로 GC대상에서 제외할 객체를 식별한다
Clean up : STW해두고 살아있는 객체가 가장 적은 Region에대한 미사용객체를 제거한다
Copy: GC대상의 Region이 Cleanup과정에서 완전히 비워지지않은 Region의 살아남은 객체들을 새로운 Available Region에 복사하여 Compaction을 수행한다
Minor GC, Major GC를 수행하고 나서도 여유공간이 부족한경우 싱글스레드로 동작하는 Full GC가 발생하므로 , 공간부족 상태를 조심해야한다
Heap공간이 작은 애플리케이션에서는 Full GC가 발생하기쉽다
Humonogous 영역의 최적화가 정밀하게 이루어지지않아서 해당영역이 많으면 성능이 떨어진다
JDK15 에서부터 프로덕션 준비가 된 상태
왜만들어졌나?
=> 적은메모리나 큰메모리에서 STW시간을 최대한 적게 가져가기위해 제작됨
1.Colored Pointers
객체를 가리키는 변수의 포인터에서 64bit라는 메모리를 활용해서 Mark를 진행하여 객체의 상태값을 저장하여 사용하는방식
42bit를 객체주소로 사용하고 4bit를 4가지로 나누어서 표시하였다
Finalizable: finalizer을 통해서만 참조되는 Object
Remapped : 재배치 여부를 판단하는 Mark
Marked 0 : Live Object
Marked 1 : Live Object
2.Load Barriers
Thread에서 참조객체를 Load할때 실행되는 코드
ZGC는 STW없이 동시적으로 재배치를 실행하기때문에 참조를 수정해야하는일이 일어나기때문에 Load Barriers가 RemapMark 와 RelocationSet을 확인하며 Mark를 업데이트하고 올바른 참조값으로 인도한다
ref: https://www.youtube.com/watch?v=Fe3TVCEJhzo
https://d2.naver.com/helloworld/329631
https://d2.naver.com/helloworld/1329
https://dinfuehr.github.io/blog/a-first-look-into-zgc/
https://renuevo.github.io/java/garbage-collection/