Java에서는 개발자가 프로그램 코드로 메모리를 명시적으로 해제하지 않기 때문에 가비지 컬렉터가 더 이상 필요 없는 (UnReachable) 객체를 찾아 지우는 작업을 한다.
대부분의 객체는 금방 접근 불가능한 상태(unreachable)가 된다. ⇒ minor GC
오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다. ⇒ magor GC
JVM의 Heap 영역에서 사용하지 않는 객체를 삭제하는 프로세스를 말한다.
ex) Object 타입의 데이터들, String, List 등 → Heap 영역의 Object를 가리키는 참조 변수가 Stack에 할당.
ex) 원시 타입의 데이터가 값과 함께 할당, Heap영역에 생성된 Object 타입의 데이터의 참조 값 할당.
GC Roots로부터 어떤 객체에 유효한 참조가 존재한다면 Reachable, 그렇지 않다면 Unreachable이라고 한다.
Unreachable한 객체들이 GC의 수거 대상
GC Roots : Stack 영역의 데이터들, method 영역의 static 데이터들, JNI에 의해 생성된 객체들.
Mark and Sweep
Mark : GC는 GC Root로부터 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다. (Reachable 객체와 Unreachable 객체를 식별하는 과정)
Sweep : Unreachable 객체들을 Heap에서 제거한다.
+3. (알고리즘에 따라 추가 될 수 있음) Compact : Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 나눈다. (메모리 단편화를 막아줌)
Young 영역 : 새롭게 생성한 객체의 대부분이 여기에 위치한다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질때 Minor GC가 발생한다.
Old 영역 : 접근 불가능 상태(UnReachable)로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC가 발생한다.
2-1. Reachable 객체들은 Survivor 영역으로 이동 (0 or 1로 이동)
2-2. Sweep 실행 : UnReachable 객체들은 GC에 의해 수거
*객체들은 Survivor1 또는 Survivor2 영역에 따로 존재해야함 (Survivor 영역 중 하나는 반드시 비어 있는 상태)
Survivor 영역에 있는 객체들은 age가 증가한다. (Aging)
또 다시 Eden 영역이 꽉 차게 되면 Minor GC가 발생
이전 단계에서 Reachable 객체들이 Survivor1 영역에 있었다면 Age가 1 증가하고 Survivor2 영역으로 이동
계속 반복
객체의 age가 age threshold (임계점)에 도달하면 Old Generation으로 이동.
GC의 종류에 따라서 처리 절차가 달라지므로, 아래 GC종류에서 살펴보도록 하겠다.
GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것.
GC를 실행하는 쓰레드 외의 모든 쓰레드가 작업을 중단한다.
GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작.
대개의 경우 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것이다.
*sweep : 단일 쓰레드가 old 영역 전체를 훑는다.
*summary : 멀티 쓰레드가 old 영역을 분리해서 훑는다.
Initial Mark : GC Root에서 참조하는 객체들만 우선 식별
Concurrent Mark : 이전 단계에서 식별한 객체들이 참조하는 모든 객체 추적
(GC를 처리하는 쓰레드는 하나지만 다른 작업들도 계속계속해서 처리가 가능)
Remark : 이전 단계에서 식별한 객체를 다시 추적. 추가되거나 참조가 끊기 객체 확정
Concurrent Sweep : 최종적으로 unreachable 객체들을 삭제
자바는 OS의 메모리 영역에 직접적으로 접근하지 않고 JVM 이라는 가상머신을 이용해서 간접적으로 접근. ⇒ 메모리 관리라는 까다로운 부분을 자바 가상머신에 모두 맡겨버린 것.
이렇게 자바는 가상머신을 사용함으로써 (운영체제로부터 독립적이라는 장점 외에도) OS 레벨에서의 memory leak은 불가능하게 된다는 장점이 있다.
*자바의 메모리 누수 : 메모리 누수는 더이상 사용하지 않는 객체가 가비지 컬렉션(GC)에 의해서 회수되지 않고 계속 누적되는 현상. Old 영역에 누적된 객체로 인해서 Major GC가 빈번하게 발생하게 되고, 프로그램의 응답 속도가 늦어지다 결국 OOM(OutOfMemory) 오류로 프로그램이 종료된다.
하지만!!
memory leak이 발생할 수 있다.
그 이유는 실제로 사용 되지 않는 객체의 reference를 프로그램에서 잡고 있으면 그 객체는 GC에 의해 처리되지 않고 프로그램내에서도 접근하여 사용될 수 없는 사실상 쓰레기로서 메모리(주소 공간)를 점유하게 된다.
자바의 사용되는 메모리란 사용될 가능성이 있다는 것 일뿐이므로 논리적으로도 정확하게 사용되고 있는 객체가 아닌 사실상의 쓰레기 객체가 있을 수 있으며 이러한 객체들이 메모리 누수 현상을 초래.
EX) 빈번한 전역변수의 선언, 리스트나 해쉬맵 같은 컬렉션에 저장한 객체를 해제하지 않고 계속 유지하게 되면 주로 발생
메모리 누수가 발생하는 상황들은 이 사이트에 잘 정리되어 있다.
아직은 대규모 프로젝트를 경험한 적이 없어서 GC 튜닝이라는 주제가 많이 와닿지 않지만 그래도 GC에 대해 깊은 이해가 있어야 훌륭한 Java 개발자가 될 수 있다고 생각한다. GC가 어떤 방식으로 동작하는지, 튜닝을 어떤 방식으로 해야하는지, 메모리 누수는 어떤 현상에서 발생하는지 알아야 일정 규모의 프로젝트 진행시 조금 더 성능이 개선된 코드를 작성할 수 있다고 생각한다.
참고자료
https://d2.naver.com/helloworld/1329
https://www.youtube.com/watch?v=Fe3TVCEJhzo