Language/Java

[Java] Garbage Collector

김마루 2025. 10. 21. 20:22

GC란?

C/C++ 언어에서는 개발자가 mallc/free 또는 new/delete로 직접 메모리를 관리해야 한다.
하지마 Java는 JRE 구성요소 중 하나인 Garbage Collertor(GC)가 존재하여,
개발자가 명시적으로 객체를 해제 하지 않아도 사용하지 않는 객체를 자동으로 메모리에서 제거한다.


그럼 사용하지 않는 객체는 어떻게 판단하냐면

1. 객체가 NULL인 경우

2. 블럭 실행 종료 후, 블록에서 생성된 객체
3. 부모 객체가 NULL인 경우, 포함하는 자식 객체

일 경우에 해당 객체들은 GC의 대상이 된다.

 

 

 

GC는 JVM의 힙(Heap) 영역에서 동작하며, 참조가 끊어진 객체를 찾아 제거한다.
이때 GC를 수행하기 위해 JVM이 애플리케이션 실행을 일시 중지하는 현상을 Stop-The-World(STW)라고 한다.
GC 튜닝의 핵심 목표는 이 STW 시간을 최소화하는 것이다.

 

 

 

GC의 기본 과정은 다음 3단계로 이루어진다.


Mark

이 과정은 해당 객체가 메모리에 사용되는지 아닌지 찾아내는 과정으로 GC ROOT부터 시작하여
참조가 닿는 객체들은 모두 표시를 해둔다.

Sweep

Mark 과정에서 참조되지 않는 객체를 찾아 메모리를 회수한다.

Compact

압축단계로 삭제된 객체들로 힙이 듬성듬성 비게되면 단편화가 발생한다. 힙을 효율적으로 사용하기 위해

남은 객체들은 한쪽으로 모아 단편화(fragmentation)를 줄여 연속된 공간을 확보한다.



이러한 과정 덕분에 메모리 누수를 최소화할 수 있지만, 객체 수가 많을수록 탐색 시간이 증가하므로 효율 개선이

필요하며, 해당 과정은 오래걸리는데 실제 객체는 더 빨리 죽는다.
이러한 관점에서 나온 것이 Weak Generation Hypothesis이다.

 

 

 

 

 

 

 

 

 

Weak Generation GC란?

위의 Weak Generation Hypothesis가설을 바탕으로 설계된 것이 Generation GC이다.
해당 방법은 힙 영역을 3가지로 나눠서 처리한다.

 

 

 

 

 

 

 

Young 영역

새롭게 생성한 객체의 대부분은 해당 영역에 위치하며 금방 소멸한다.
Eden -> Survivor 0/1 순으로 이동하며 살아남은 객체는 Old 영역으로 승급된다.
해당 영역에서 발생하는 GC는 Minor GC이다.


Old 영역

Young 영역에서 오래 살아남는 객체들이 존재한다. 크기가 크고 GC가 상대적으로 적게 발생한다.
해당 영역에서 발생하는 GC는 Major GC(Full GC)이다.

 

 

Permanent 영역

클래스, 메서드 정보 등 메타데이터를 저장하며, Java8 이후로는 Metaspace로 대체되었다.

 

 

 

 

 

 

Generation GC 과정

GC는 총 2가지 종류가 있다.
Minor Gc, Major GC

 

 

 

 

minor GC의 발생 조건

기본적으로 Minor GC는 Young 영역에서 발생하며 Eden 영역이 객체로 가득 찾을때 발생한다.

 

 

 

 

새로운 객체가 생성되어 Eden 영역에 들어오게 된다.

 

 

 

 

 

 

Eden Space가 가득차게 되면 minor GC가 시작된다.

 

 

 

 

 

 

참조되는 객체들은 Eden -> S0으로 이동하고,

참조되지 않는 객체들은 Eden 영역이 clear 될때 한번에 반환된다.

 

 

 

 

 

다시 한번 Eden에 객체가 가득  차서 minro GC가 발생했다.
살아있는 객체(Old: 1)들은 Eden -> S1으로 이동하고
S0에 있는 객체(Old: 2)는 S0 -> S1로 이동하게 된다.

S0, S1중 한쪽 공간에서 참조되는 객체가 없을 경우  S0과 Eden는 clear가 된다.
 

 

 

 

 

 

계속해서 minor GC가 반복되게 되고 이 과정에서 참조되지 않는 객체들은 소멸하고
참조하는 객체와 새로 생성된 객체들은 Age가 점점 증가하게 된다.





 

 


이렇게 minor GC가 계속 발생하다가 일정 조건(age threshold)가 발생하면 해당 객체들은
young 영역에서 old 영역으로 승급을 하게 된다.
현재 threshold는 9이다.

 

 

 

 

 

 

 

major GC의 발생 조건

 

minor GC로 old 영역에 객체들이 계속 쌓여서 Old 영역의 메모리가 부족해질 때 발생하며,
처리시간이 minor GC보다 처리시간이 길다. 처리시간이 길디 때문에 GC가 진행될때의 선행조건인

STW도 같이 길어지기 때문에 major GC는 애플리케이션의 성능 저하로 이어질 수 있다.






 

STW(stop the world)
이전에서 설명했던 개념으로 JVM에서 발생하는 가비지 컬렉션 작업 중에 일어나는 일시적인 중단이다.
이는 애플리케이션의 모든 스레드를 정지시키는 것을 의미하며 가비지 컬렉션 작업을 위해 힙 메모리를 정리하고 
객체의 생명 주기를 관리한다.

 

 

 

 

 

객체의 생성, 소멸

객체가 생성되고 소멸되는 과정에서 어떤 메소드들이 호출될까?


객체의 생성

객체는 new 키워드로 힙 메모리에 생성되며 다음 순서로 초기화 된다.
클래스 초기화 -> 인스턴스 초기화 블록 -> 생성자 호출

 

객체 생성은 비교적 비용이 적지만, GC가 잦아지면 STW로 인해 전체 애플리케이션이 멈추는 부작용이 생긴다.
따라서 불필요한 객체 생성을 최소화 하는 것이 중요하다.

 

 

 

객체의 소멸

객체가 더 이상 참조되지 않으면 GC의 대상이 된다.
이때 다음 메소드들이 관여할 수 있다.


finalize()

객체가 GC로 해제되기 직전에 호출되지만, 실행 시점이 불확실하고, 외부 자원을 제대로 해제하지 못할 수 있어서
현재는 Deprecated 된 상태이다.


close()

파일, 소켓 등 외부 자원은 GC가 직접 관리하지 못하기 때문에 try-with-resources나 명시적으로 close를 하여
개발자가 직접 해제해야 된다.

 

 

 

 

 

여기서 객체의 생성과 소멸이 왜 튀어나왔냐면 바로 GC와 관련이 있다.
보통 자바의 경우 GC로 인하여 자동으로 메모리가 회수된다. 다만 자바에서는 예전에 Object 클래스에 정의된

finalize()메소드를 제공하여 개발자가 수동으로 호출할 수 있게 개발되었다.


원래의 정상 로직에서는 finalize()는 객체가 소멸되기 직전에 GC가 해당 객체를 수집하기전에 자동으로 호출된다.
호출된 finalize()는 각 객체와 연결된 외부 자원 연결을 해제하여 닫힌 상태로 만들고, 해당 객체가 더 이상 외부자원과
연결되지 않았을때 비로소 GC로 인해서 힙에서 메모리 할당이 해제된다. 이렇게 원래는 자원할당, 힙 메모리 해제를
구분지어서 JVM이 객체 생명주기 관리에 맞게 동작된다.


다만 객체의 소멸과 별도로 개발자가 직접 obj.finalize()를 호출할 수 있는데 이 경우 아직 사용되고 있는 객체가 
외부 자원으로부터의 연결이 끊기게 된다. 즉, 객체는 살아 있지만 속은 비어있는 상태가 되기 때문에
해당 객체를 사용할때 예상치 못한 오류를 발생하게 된다.

 

 

위 경우 객체는 여전히 참조 가능한 상태인데 마치 GC에 의해 외부자원이  정리된 것처럼 보이게 되고
그 결가 객체의 상태와 메모리 관리 상태 불일치가 일어난다. 또한 finalize() 메소드 자체는 호출을 해도
실행 시험이 불명확하다는 단점이 존재하기 때문에 자원이 늦체 해제되거나 해제되지 않는 문제가 발생한다.


이러한 점을 방지하기 위해 try-catch문을 통한 close()메소드로 자원을 직접 할당 되는 방법이 권장되고 있다.