- 가비지 컬렉터의 수거 대상은 무슨 근거로 판단할까?
- 왜 heap 은 여러 영역으로 나눠져 있을까?
다음과 같은 궁금증이 생겨 해당 내용을 포스팅하게 되었습니다.
가비지 컬렉션이란?
힙영역에서 사용 중인 객체와 사용 중이지 않는 객체를 식별하고 사용하지 않는 객체를 수거하여 메모리를 관리하는 기법입니다.
가비지 컬렉션의 루트 - 모든 객체 트리에 근원
가비지란 사용하지않는 객체를 말한다.
사용하지 않는 객체란 무엇일까?
사용하지 않는 객체란 GC Root와 관계가 있다. 어떤 객체가 유효한 참조가 존재한다면 'Reachable'그렇지 않으면 'Unreachable'이라 하며 'Unreachable' 한 객체는 GC의 대상이 된다.
객체는 다른 여러 객체를 참조하고 그 객체들도 다른 객체들을 참조하므로 객체는 참조 트리를 이룬다. 참조 트리에서 유효한 참조인지 확인하기 위해서 항상 유효한 최초의 객체가 이를 'GC Root' 라 한다.
garbage 대상인지 어떻게 확인할까?
GC root 참조 유무로 판단한다. 다른 객체가 참조하고 있더라고 root 참조가 없으면 한 번에 가비지 컬렉팅 대상이 된다.
힙에 있는 객체들에 대한 참조는 4가지 종류 중 하나이다.
- 힙 내의 다른 객체에 의한 참조(순환참조)
- Java stack, Java 메서드 실행 시에 사용되는 지역 변수와 파라미터들에 의한 참조
- Native Stack , 즉 JNI (Java Native Interface)에 의해 생성된 객체에 대한 참조
- 메서드 영역의 정적 변수에 의한 참조
위의 4가지 중 순환참조(힙 내의 다른 객체에 의한 참조)는 외부에서 참조하는 것이 아니기에 reachable vs unreachable 상태를 판단짓는 용도로 사용되지 않습니다. 즉 Root Space 에서 참조되고 있느냐는 나머지 3가지 참조에 대해서 결정됩니다.
Mark and Sweep
mark and sweep 알고리즘은 객체들을 reachable 상태와 unreachable 상태로 구분해서 GC 동작 시 unreachable 상태의 객체들이 차지하고 있는 메모리를 회수합니다.
1. root space 부터 시작해 살아있는 모든 객체를 탐색하고 발견된 모든 객체를 살아있다고 mark 합니다.
2. 힙메모리내에 마킹되지 않는 객체들은 삭제(Sweep) 됩니다.
위의 그림을 보면 객체들이 Root Space (혹은 Root set)에 참조되어 있으면 reachable object라고 하고, 참조되고 있지 않다면 Unreachable objects라고 합니다.
참조되고 있다면 reference count 가 1 증가합니다.
- reachable 객체
- Root space 에서 참조되고 있는 개체
- reference count 가 0 이 아닌 객체
- unreachable 객체
- Root space 에서 참조되고 있는 객체
- reference count 가 0 이 객체
Stop the world
GC에 대해 이해하려면, Stop-the-world에 대해 이해해야 합니다. GC 가 동작하기 위해 GC를 제외한 스레드가 잠시 멈추는 Stop-the-world 가 발생합니다.
GC 가 동작함으로써 JVM 의 메모리를 확보할 수 있다는 장점도 있지만 다른 스레드들이 동작하지 못하게 돼 프로그램의 성능 저하는 준다는 것입니다. 그래서 stop-the world 시간이 gc 성능에 중요한 요소입니다.
Minor GC와 Major GC 모두 실행될 때에는 stop-the-world 발생하지만 Minor GC 는 상대적으로 작은 Young 영역에 대해 GC 가 발생하기 때문에 stop-the-world 시간이 매우 짧지만, Major GC(혹은 Full GC)의 경우에는 비교적 큰 Old 영역에 대해 GC 가 발생하기 때문에 GC 가 오랜 시간 동안 지속됩니다.
Stop the world 는 언제 발생할까?
GC 알고리즘에 따라 다르겠지만 주로 마킹과정에서 발생합니다.
gc 도중 쓰레드가 루트참조를 할 수 있기 때문에 마킹할 때 root 참조가 없다는 것을 확실히 판단하기 위해 stop the world 가 발생합니다.
그 외 과정은 주로 병렬로 처리됩니다.
힙 영역은 왜 세분화되어있을까?
jvm 영역은 GC 가 효율적으로 동작할 수 있게 힙영역이 세분화되어 있습니다. 크게 Young, Old로 나눌 수 있고, Young 영역에는 Eden, Survivor0 , Survivor01 영역으로 나뉩니다.
GC를 만들고 힙 영역을 세분화할 때는 weak generational hypothesis라는 가설을 토대로 만들어졌다고 합니다. 가설의 내용은 아래와 같습니다.
대부분의 객체는 금방 접근 불가능한 상태(unreachable)가 된다.
오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
그래서 JVM의 힙 영역을 세분화시킴으로써 GC의 효율을 극대화시킨 것입니다.
먼저 영 제너레이션은 전체 힙의 일부분일 뿐이므로 이와 같이 처리하면 전체힙을 처리하는 것보다 더 빠릅니다. 이건 애플리케이션 스레드가 전체 힙이 한 번에 처리되는 것보다 더 짧은 기간 동안만 중지된다는 것을 의미합니다. 즉 전체 힙이 가득 찰 때까지 JVM 이 GC를 수행하려고 대기할 때보다 애플리케이션 스레드가 더 자주 멈춘다는 의미이므로 여기서 트레이트 오프를 해야 합니다. 하지만 더 자주 중지되더라도 대체로 짧게 처리되는 편이 더 이익입니다.
그래서 Young 영역은 Old 영역의 사이즈보다 작게 유지한 채, 대부분의 객체들이 Young 영역에서 메모리가 회수되도록 구현되어 있습니다.
다만 몇몇 객체들은 Young 영역에서 계속 살아남아 Old 영역으로 복사되는데 이 과정을 aging 이라고 합니다.
- 부분의 GC 알고리즘에서는 age를 임계값으로 두고, 특정 age가 되면 Old 영역으로 복사됩니다.
그리고 Young 영역에서 발생한 GC는 Minor GC라 부르고, Old 영역에서 발생한 GC는 Major GC(Full GC)라고 부릅니다.
Stop-the-world는 언제 일어날까?
대부분의 GC 알고리즘에서 루트탐색단계에서 STW 가 발생합니다.
GC 가 객체참조를 추적하거나 메모리에 객체를 옮긴다면 애플리케이션 스레드가 그 객체를 사용하고 있지 않다는 사실을 분명히 해야 합니다. 이건 특히 GC로 인해 주변 객체가 이동하면서 해당 동작이 일어나는 동안 객체의 메모리상의 위치가 변경되고 그로 인해 그 객체가 접근할 수 있는 애플리케이션 스레드가 없을 때 적용됩니다.
참고자료
https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works/
https://velog.io/@ddangle/Java-GC-Garbage-Collector% EC%97%90-%EB% 8C%80% ED%95% B4
'JAVA' 카테고리의 다른 글
[JAVA] 제네릭 (Generic) (0) | 2024.11.08 |
---|---|
[자바의 신2] 정리해봅시다 [2장~ 11장] (0) | 2024.11.07 |
[JAVA] Error 와 Exception (0) | 2024.11.01 |
[JAVA] staic 과 final (1) | 2024.10.29 |
== 와 equals 차이 , hashcode (0) | 2024.10.24 |