- 지금까지 알고있었던 가비지 컬렉터에 대한 내용
가비지 컬렉터는 더이상 참조하지 않아 필요없는 객체를 메모리(힙)에서 해제하는 것을 말한다.
📌가비지 컬렉터의 진행과정은?
- 맨처음 객체가 생성되면 Eden영역에 생성됩니다.
- Eden 영역이 채워지면 GC의 일종인 minor GC가 발생하여 미사용객체의 제거와 함께 아직 사용되고 있는 객체를 survivor 0 또는 1에 이동시킵니다.
( survivor 0 / 1 중 반드시 하나는 비워져 있어야 하고 만약, 객체의 크기가 survivor보다 크면 바로 old generation영역으로 이동됩니다)
- 객체가 존재하는 survivor영역이 가득차면 다른 survivor영역으로 보내고 기존의 survivor영역은 비우는 작업이 수행됩니다. 이 과정이 계속 반복됩니다.
- survivor영역에서 계속 살아남은 객체들에게 일정 score이 누적되어 기준치 이상이 되면 old generation 영역으로 이동하게 됩니다.
- old generation영역에서 살아남았던 객체들이 일정 수준 쌓이게 되면 미사용으로 판단된 객체를 제거해주는 Full GC가 발생합니다. 이 과정에서 STW(stop the world)가 발생합니다.
→ 이 진행 과정에 의해 가비지 컬렉터가 객체를 바로 해체하지 않는다고 판단했다.
하지만, 멘토링을 통해 가비지 컬렉터에서 레퍼런스 카운팅(=참조횟수) 에 대한 지식이 빠져 있는 것을 확인하였다. 즉, “언제 객체가 더이상 참조되지 않는다고 판단되는 가?”에 대한 지식이 명확하지 않았다.
📌레퍼런스 카운팅(참조 횟수)
- **같은 객체를 여러 곳에서 참조할 때, 참조하는 곳의 수를 나타낸다.
- 몇 번이나 참조되고 있는지 판단 가능하게 한다.
Scope(중괄호{})를 벗어나면 참조를 하지 않는다고 판단하여 참조 횟수가 줄어들고, 참조 횟수가 0이 되면 GC가 더이상 참조하지 않는 객체(Garbage)로 판단하여 메모리에서 해제하는 작업을 한다. 스마트 포인터의 std::shared_ptr도 레퍼런스 카운팅 방식으로 동작한다.
(pass by reference는 함수에 값을 넘기면서 참조횟수도 증가시키는 역활도 한다는 것을 알 수 있다.)
📖왜 참조하는 횟수만을 나타내는가?
참조하는 장소의 정확한 정보를 다루지 않고 단지 참조하는 곳의 수만을 셀까?
더 복잡하고 더 많은 정보를 다룰수록 어플리케이션 성능이 떨어진다는 문제가 발생하기 때문이다.
📖한계점
순환 참조 즉, 2개 이상의 객체가 서로를 참조하는 경우에는 레퍼런스 카운팅을 잘못하여 메무리 누수의 요인이 된다는 문제점이 있어 Mark and sweep 이 등장하게 된다. 순환 참조는 컨테이너 객체(Tuple, List, Set, Dict, Class)에 의해서만 발생합니다. 컨테이너 객체들만이 다른 객체의 참조를 보유할 수 있기 때문이다.
📌Mark and sweep
📖진행 과정
-
GC Root에서 시작해 이 Root가 참조하는 모든 오브젝트, 또 그 오브젝트들이 참조하는 다른 오브젝트들을 탐색해 내려가며 접근할 수 있는 모든 객체들을 마크(Mark)한다.
-
Mark가 끝나면 가비지 컬렉터는 힙 내부를 전체를 돌면서 Mark되지 않은 메모리들을 해제한다(Sweep)
📖GC Root가 될 수 있는 것들은?
- 실행중인 쓰레드 (Active Thread)
- 정적 변수 (Static Variable)
- 로컬 변수 (Local Variable)
- JNI 레퍼런스 (JNI Reference)
📖Mark and sweep의 한계점
- GC를 수행하는 동안 Stop the World(이하 STW)가 발생한다.
- 프로그램에서 여러번 실행되면 도달 가능한 객체가 많은 미사용 메모리 영역들로 인해 분리된다.(메모리 파편화)
💡 JVM의 GC 알고리즘의 중요 목적
1) 어떻게 불필요한 object들을 선별 하는 가
2) GC가 동작하는 동안 프로그램이 중단 되는 시간을 어떻게 줄일 수 있는가