JAVA는 JVM을 통하여 구동된다. JAVA의 특징 중 하나로 메모리 관리를 개발자가 직접 명시적으로 수행하지 않고, JVM에서 자동으로 수행해 주며, 이와 같은 역할을 하는 프로세스를 Garbage Collector(GC)라 한다.
자바에서는 메모리를 GC라는 알고리즘을 통하여 관리(automatic memory management)하기 때문에, 개발자가 메모리를 처리하기 위한 로직을 만들 필요가 없고, 절대로 만들어서는 안된다.
Garbage Collection은 JAVA에서 쓰레기는 객체이다. 하나의 객체는 메모리를 점유하고, 필요하지 않으면 메모리에서 해제되어야 한다. 특정 시점 이후에는 새로운 객체 생성을 위한 충분한 메모리가 확보되지 않고 Out Of Memory Error로 인해 전체 프로그램이 비정상적으로 종료되어 서비스 장애로 까지 이어 질수 있다.
GC는 JVM의 백그라운드에서 자동적으로 메모리 관리를 수행하는 JVM의 기능이다. 이러한 GC는 JVM의 메모리 영역 중 Heap 메모리에서 이뤄지는데,
그 이유는 Java에서 새로운 객체가 할당될 때, Heap 메모리 영역에 할당 되기 때문이다.
JAVA의 GC는 다음과 같은 가설을 전재로 도입되었다
JAVA에서 GC의 도입의 전제 가설 (weak generational hypothesis)
- 대부분의 객체는 금방 접근 불가능 상태 (unreachable)가 된다.
- 오래된 객체에서 젋은 객체로의 참조는 아주 적게 존재한다.
아래와 같이 객체를 생성하고 해당 객체의 어떤 동작을 10,000번 수행하는 반복문에서 마지막 한번을 제외 한 9,999번의 객체들은 생성 후 바로 접근 불가능 상태가 된다. 이를 unreachable 상태라고 하며, GC가 발생하지 않으면 10,000개의 객체 현재 참조 되지 않지만 메모리를 점유 하고 있다고 볼 수 있다.
for (int i = 0; i < 10000; i++) {
SampleObj obj = new SampleObj();
obj.doTest(i);
}
또 아래와 같이 객체를 생성하고 다른 메소드나 클래스에 해당 객체를 전달 후 에는 다시 참조 하지 않는 경우가 대 부분이다. 이를 오래된 객체에서 젊은 객체로의 참조는 아주 적게 발생 한다라고 말 할 수 있다.
SampleObj obj = new SampleObj();
obj.setValue(1);
doTest2(obj);
GC 작업을 하는 가비지 콜렉터(Garbage Collector)는 다음의 역할을 한다.
GC를 해도 더 이상 사용 가능한 메모리 영역이 없는데 계속 메모리를 할당하려고 하면, OutOfMemoryError가 발생하여 프로그램이 종료 될 수도 있다. 행(Hang) 즉, 서버가 요청을 처리 못하고 있는 상태가 된다.
JVM의 메모리는 크게 클래스 영역, 자바 스택, 힙, 네이티브 메소드 스택의 4개 영역으로 나뉜다. 가비지 콜렉터에서는 힙 메모리를 다루게 됩니다. 즉 자바콜렉터가 인식하고 할당하는 자바 메모리 영역은 힙 영역이다.
Young, Old, Perm 세 영역으로 나뉜다. 이 중 Perm(Permanent) 영역은 거의 사용되지 않는 영역으로서 클래스와 메소드 정보와 같이 자바 언어 레벨에서 사용되지 않는다.
Yong, Old 영역 그리고 Young 영역은 Eden영역과 두 개의 Survivor 영역으로 나뉜다.
우리가 고려해야 할 자바의 메모리 영역은 총 4개의 영역으로 나뉜다고 볼 수 있다. ( Young (1. Eden, 2. Survivor 1, 3. Survivor 2), Old (4. 메모리) )
일단 메모리에 객체가 생성되면, Eden 영역에 객체가 지정된다. Eden 영역에 데이터가 어느 정도 쌓이면, 이 영역에 있던 객체가 어디론가 옮겨지거나 삭제된다. 이 때 옮겨가는 위치가 survivor 영역이다. 두개의 Survivor 영역 사이에 우선 순위가 있는 것은 아니다. 하지만, 이 두 개의 영역 중 한 영역은 반드시 비어 있어야 한다. 그 비어있는 영역에 Eden 영역에 있던 객체가 할당된다.
Eden에서 survivor 둘 중 하나의 영역으로 할당 되고, 할당된 Survivor 영역이 차면, GC가 되면서 Eden 영역에 있는 객체와 꽉 찬 Survivor 영역에 있는 객체가 비어 있는 Survivor 영역으로 이동한다. 그러다가 더 큰 객체가 생성되거나, 더 이상 Young 영역에 공간이 남지 않으면 객체들은 Old 영역으로 이동하게 된다.
Young 영역과 Old 영역은 서로 다른 메모리 구조로 되어 있기 때문에, 세부적인 동작 방식은 다르다. 하지만 기본적으로 가비지 컬렉션이 실행된다고 하면 다음의 2가지 공통적인 단계를 따르게 된다.
Stop The World
Stop The World는 가비지 컬렉션을 실행하기 위해 JVM이 애플리케이션의 실행을 멈추는 작업이다. GC가 실행될 때는 GC를 실행하는 쓰레드를 제외한 모든 쓰레드들의 작업이 중단되고, GC가 완료되면 작업이 재개된다. 당연히 모든 쓰레드들의 작업이 중단되면 애플리케이션이 멈추기 때문에, GC의 성능 개선을 위해 튜닝을 한다고 하면 보통 stop-the-world의 시간을 줄이는 작업을 하는 것이다. 또한 JVM에서도 이러한 문제를 해결하기 위해 다양한 실행 옵션을 제공하고 있다.
GC가 수행되는 동안, GC를 수행하는 스레드 이외의 스레드가 중단되어 애플리케이션 중단이 발생하는데, 이를 Stop the World라고 한다.
Mark and Sweep
GC는, 참조되는 변수를 구별하여(Mark), 참조되지 않는 변수를 해제(Sweep)하는 식으로 이뤄진다.
GC가 수행되는 영역에 따라 Minor GC 와 Major GC(Full GC) 로 구분한다.
Minor GC는 Eden과 Survivor1,2의 Young Generation 영역의 객체를 메모리에서 삭제한다.
(Young 영역에서 발생하는 GC)
Major GC는 Minor GC과정에서 삭제 되지 않고, Old Generation영역으로 옮겨진 객체 중 미사용 된다고 판단되는 객체를 메모리에서 삭제한다.
(Old 영역이나 Perm 영역에서 발생하는 GC)
GC의 성능을 최적화하기 위하여 Heap 메모리 영역이 구분되어 있는데,
크게 Young & Old Generation으로 나눌 수 있고 각 영역에서 Minor GC와 Major GC가 수행된다.
GC는 기본적으로 다음과 같은 시나리오로 동작한다.
객체가 생성되면 Eden 영역에 위치 하게 된다.
Eden영역이 가득차게 되면 Minor GC가 발생하여 참조가 없는 객체는 삭제되고, 참조 중인 객체는 Suvvivor 영역으로 이동한다.
Survivor영역이 가득차게 되면 Minor GC가 발생하고 참조가 없는 객체는 삭제되고, 참조 중인 객체는 다른 Suvvivor 영역으로 이동한다.
Survivor영역에서의 GC과정을 반복 하며, 계속 참조 중인 객체는 OLD 영역으로 이동한다.
Eden 영역에서 Survivor 영역으로 이동 할 때 객체가 남아있는 영역보다 큰 경우 OLD 영역으로 이동한다.
JVM 버전의 변화에 따라 여러가지 GC방식이 추가 되고 발전되어 왔다.
버전별로 지원하는 GC는 차이가 존재하며, 서비스 상황에 따라 필요한 GC방삭을 JVM옵션을 통한 설정으로 사용이 가능하다.
GC의 종류는 다음과 같다.
https://mangkyu.tistory.com/118
https://d2.naver.com/helloworld/1329
https://blog.ycpark.net/entry/JAVA%EC%9D%98-GC%EC%9D%98-%EC%A2%85%EB%A5%98-%EB%B0%8F-%ED%8A%B9%EC%A7%95
https://12bme.tistory.com/57
https://www.freecodecamp.org/news/garbage-collection-in-java-what-is-gc-and-how-it-works-in-the-jvm/
https://www.oracle.com/java/technologies/javase/gc-tuning-6.html#introduction